diff options
Diffstat (limited to 'toolkit/modules/RemotePageAccessManager.sys.mjs')
-rw-r--r-- | toolkit/modules/RemotePageAccessManager.sys.mjs | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/toolkit/modules/RemotePageAccessManager.sys.mjs b/toolkit/modules/RemotePageAccessManager.sys.mjs new file mode 100644 index 0000000000..d6795e9046 --- /dev/null +++ b/toolkit/modules/RemotePageAccessManager.sys.mjs @@ -0,0 +1,376 @@ +/* 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/. */ + +/* + * RemotePageAccessManager determines which RPM functions a given + * about page is allowed to access. It does this based on a map from about + * page URLs to allowed functions for that page/URL. + * + * An RPM function will be exported into the page only if it appears + * in the access managers's accessMap for that page's uri. + * + * This module may be used from both the child and parent process. + * + * Please note that prefs that one wants to update need to be + * explicitly allowed within AsyncPrefs.sys.mjs. + */ +export let RemotePageAccessManager = { + /* The accessMap lists the permissions that are allowed per page. + * The structure should be of the following form: + * <URL> : { + * <function name>: [<keys>], + * ... + * } + * For the page with given URL, permission is allowed for each + * listed function with a matching key. The first argument to the + * function must match one of the keys. If keys is an array with a + * single asterisk element ["*"], then all values are permitted. + */ + accessMap: { + "about:certerror": { + RPMSendAsyncMessage: [ + "Browser:EnableOnlineMode", + "Browser:ResetSSLPreferences", + "GetChangedCertPrefs", + "Browser:OpenCaptivePortalPage", + "Browser:SSLErrorGoBack", + "Browser:PrimeMitm", + "Browser:ResetEnterpriseRootsPref", + "DisplayOfflineSupportPage", + ], + RPMRecordTelemetryEvent: ["*"], + RPMAddMessageListener: ["*"], + RPMRemoveMessageListener: ["*"], + RPMGetFormatURLPref: ["app.support.baseURL"], + RPMGetBoolPref: [ + "security.certerrors.mitm.priming.enabled", + "security.certerrors.permanentOverride", + "security.enterprise_roots.auto-enabled", + "security.certerror.hideAddException", + "network.trr.display_fallback_warning", + ], + RPMGetIntPref: [ + "security.dialog_enable_delay", + "services.settings.clock_skew_seconds", + "services.settings.last_update_seconds", + ], + RPMGetAppBuildID: ["*"], + RPMGetInnerMostURI: ["*"], + RPMIsWindowPrivate: ["*"], + RPMAddToHistogram: ["*"], + }, + "about:home": { + RPMSendAsyncMessage: ["ActivityStream:ContentToMain"], + RPMAddMessageListener: ["ActivityStream:MainToContent"], + }, + "about:httpsonlyerror": { + RPMGetFormatURLPref: ["app.support.baseURL"], + RPMSendAsyncMessage: ["goBack", "openInsecure"], + RPMAddMessageListener: ["WWWReachable"], + RPMTryPingSecureWWWLink: ["*"], + RPMOpenSecureWWWLink: ["*"], + }, + "about:certificate": { + RPMSendQuery: ["getCertificates"], + }, + "about:neterror": { + RPMSendAsyncMessage: [ + "Browser:EnableOnlineMode", + "Browser:ResetSSLPreferences", + "GetChangedCertPrefs", + "Browser:OpenCaptivePortalPage", + "Browser:SSLErrorGoBack", + "Browser:PrimeMitm", + "Browser:ResetEnterpriseRootsPref", + "ReportBlockingError", + "DisplayOfflineSupportPage", + "OpenTRRPreferences", + ], + RPMCheckAlternateHostAvailable: ["*"], + RPMRecordTelemetryEvent: ["security.doh.neterror"], + RPMAddMessageListener: ["*"], + RPMRemoveMessageListener: ["*"], + RPMGetFormatURLPref: [ + "app.support.baseURL", + "network.trr_ui.skip_reason_learn_more_url", + ], + RPMGetBoolPref: [ + "security.certerror.hideAddException", + "security.xfocsp.errorReporting.automatic", + "security.xfocsp.errorReporting.enabled", + "network.trr.display_fallback_warning", + ], + RPMSetBoolPref: [ + "security.xfocsp.errorReporting.automatic", + "network.trr.display_fallback_warning", + ], + RPMAddToHistogram: ["*"], + RPMGetInnerMostURI: ["*"], + RPMGetHttpResponseHeader: ["*"], + RPMIsTRROnlyFailure: ["*"], + RPMIsFirefox: ["*"], + RPMIsNativeFallbackFailure: ["*"], + RPMGetTRRSkipReason: ["*"], + RPMGetTRRDomain: ["*"], + RPMIsSiteSpecificTRRError: ["*"], + RPMSetTRRDisabledLoadFlags: ["*"], + RPMSendQuery: ["Browser:AddTRRExcludedDomain"], + RPMGetIntPref: ["network.trr.mode"], + }, + "about:newtab": { + RPMSendAsyncMessage: ["ActivityStream:ContentToMain"], + RPMAddMessageListener: ["ActivityStream:MainToContent"], + }, + "about:plugins": { + RPMSendQuery: ["RequestPlugins"], + }, + "about:pocket-saved": { + RPMSendAsyncMessage: ["*"], + RPMAddMessageListener: ["*"], + RPMRemoveMessageListener: ["*"], + RPMGetStringPref: ["extensions.pocket.site"], + }, + "about:pocket-signup": { + RPMSendAsyncMessage: ["*"], + RPMAddMessageListener: ["*"], + RPMRemoveMessageListener: ["*"], + RPMGetStringPref: ["extensions.pocket.site"], + }, + "about:pocket-home": { + RPMSendAsyncMessage: ["*"], + RPMAddMessageListener: ["*"], + RPMRemoveMessageListener: ["*"], + RPMGetStringPref: ["extensions.pocket.site"], + }, + "about:pocket-style-guide": { + RPMSendAsyncMessage: ["*"], + RPMAddMessageListener: ["*"], + RPMRemoveMessageListener: ["*"], + }, + "about:privatebrowsing": { + RPMSendAsyncMessage: [ + "OpenPrivateWindow", + "SearchBannerDismissed", + "OpenSearchPreferences", + "SearchHandoff", + ], + RPMSendQuery: [ + "IsPromoBlocked", + "ShouldShowSearch", + "ShouldShowSearchBanner", + "ShouldShowPromo", + "SpecialMessageActionDispatch", + ], + RPMAddMessageListener: ["*"], + RPMRemoveMessageListener: ["*"], + RPMGetFormatURLPref: [ + "app.support.baseURL", + "browser.privatebrowsing.vpnpromourl", + ], + RPMIsWindowPrivate: ["*"], + }, + "about:protections": { + RPMSendAsyncMessage: [ + "OpenContentBlockingPreferences", + "OpenAboutLogins", + "OpenSyncPreferences", + "ClearMonitorCache", + "RecordEntryPoint", + ], + RPMSendQuery: [ + "FetchUserLoginsData", + "FetchMonitorData", + "FetchContentBlockingEvents", + "FetchMobileDeviceConnected", + "GetShowProxyCard", + "FetchEntryPoint", + "FetchVPNSubStatus", + "FetchShowVPNCard", + ], + RPMAddMessageListener: ["*"], + RPMRemoveMessageListener: ["*"], + RPMSetBoolPref: [ + "browser.contentblocking.report.show_mobile_app", + "browser.contentblocking.report.hide_vpn_banner", + ], + RPMGetBoolPref: [ + "browser.contentblocking.report.lockwise.enabled", + "browser.contentblocking.report.monitor.enabled", + "privacy.socialtracking.block_cookies.enabled", + "browser.contentblocking.report.proxy.enabled", + "privacy.trackingprotection.cryptomining.enabled", + "privacy.trackingprotection.fingerprinting.enabled", + "privacy.trackingprotection.enabled", + "privacy.trackingprotection.socialtracking.enabled", + "browser.contentblocking.report.show_mobile_app", + "browser.contentblocking.report.hide_vpn_banner", + "browser.vpn_promo.enabled", + ], + RPMGetStringPref: [ + "browser.contentblocking.category", + "browser.contentblocking.report.monitor.url", + "browser.contentblocking.report.monitor.sign_in_url", + "browser.contentblocking.report.manage_devices.url", + "browser.contentblocking.report.proxy_extension.url", + "browser.contentblocking.report.lockwise.mobile-android.url", + "browser.contentblocking.report.lockwise.mobile-ios.url", + "browser.contentblocking.report.mobile-ios.url", + "browser.contentblocking.report.mobile-android.url", + "browser.contentblocking.report.vpn.url", + "browser.contentblocking.report.vpn-promo.url", + "browser.contentblocking.report.vpn-android.url", + "browser.contentblocking.report.vpn-ios.url", + ], + RPMGetIntPref: ["network.cookie.cookieBehavior"], + RPMGetFormatURLPref: [ + "browser.contentblocking.report.monitor.how_it_works.url", + "browser.contentblocking.report.lockwise.how_it_works.url", + "browser.contentblocking.report.monitor.preferences_url", + "browser.contentblocking.report.monitor.home_page_url", + "browser.contentblocking.report.social.url", + "browser.contentblocking.report.cookie.url", + "browser.contentblocking.report.tracker.url", + "browser.contentblocking.report.fingerprinter.url", + "browser.contentblocking.report.cryptominer.url", + ], + RPMRecordTelemetryEvent: ["*"], + }, + "about:tabcrashed": { + RPMSendAsyncMessage: ["Load", "closeTab", "restoreTab", "restoreAll"], + RPMAddMessageListener: ["*"], + RPMRemoveMessageListener: ["*"], + }, + "about:welcome": { + RPMSendAsyncMessage: ["ActivityStream:ContentToMain"], + RPMAddMessageListener: ["ActivityStream:MainToContent"], + }, + }, + + /** + * Check if access is allowed to the given feature for a given document. + * This should be called from within the child process. + * + * The feature within the accessMap must list the given aValue, for access to + * be granted. + * + * @param aDocument child process document to call from + * @param aFeature to feature to check access to + * @param aValue value that must be included with that feature's allow list + * @returns true if access is allowed or false otherwise + */ + checkAllowAccess(aDocument, aFeature, aValue) { + let principal = aDocument.nodePrincipal; + // if there is no content principal; deny access + if (!principal) { + return false; + } + + return this.checkAllowAccessWithPrincipal( + principal, + aFeature, + aValue, + aDocument + ); + }, + + /** + * Check if access is allowed to the given feature for a given principal. + * This may be called from within the child or parent process. + * + * The feature within the accessMap must list the given aValue, for access to + * be granted. + * + * In the parent process, the passed-in document is expected to be null. + * + * @param aPrincipal principal being called from + * @param aFeature to feature to check access to + * @param aValue value that must be included with that feature's allow list + * @param aDocument optional child process document to call from + * @returns true if access is allowed or false otherwise + */ + checkAllowAccessWithPrincipal(aPrincipal, aFeature, aValue, aDocument) { + let accessMapForFeature = this.checkAllowAccessToFeature( + aPrincipal, + aFeature, + aDocument + ); + if (!accessMapForFeature) { + console.error( + "RemotePageAccessManager does not allow access to Feature: ", + aFeature, + " for: ", + aDocument.location + ); + + return false; + } + + // If the actual value is in the allow list for that feature; + // allow access + if (accessMapForFeature.includes(aValue) || accessMapForFeature[0] == "*") { + return true; + } + + return false; + }, + + /** + * Check if a particular feature can be accessed without checking for a + * specific feature value. + * + * @param aPrincipal principal being called from + * @param aFeature to feature to check access to + * @param aDocument optional child process document to call from + * @returns non-null allow list if access is allowed or null otherwise + */ + checkAllowAccessToFeature(aPrincipal, aFeature, aDocument) { + let spec; + if (!aPrincipal.isContentPrincipal) { + // For the sake of remote pages, when the principal has no uri, + // we want to access the "real" document URI directly, e.g. if the + // about: page is sandboxed. + if (!aDocument) { + return null; + } + if (!aDocument.documentURIObject.schemeIs("about")) { + return null; + } + spec = + aDocument.documentURIObject.prePath + + aDocument.documentURIObject.filePath; + } else { + if (!aPrincipal.schemeIs("about")) { + return null; + } + spec = aPrincipal.prePath + aPrincipal.filePath; + } + + // Check if there is an entry for that requestying URI in the accessMap; + // if not, deny access. + let accessMapForURI = this.accessMap[spec]; + if (!accessMapForURI) { + return null; + } + + // Check if the feature is allowed to be accessed for that URI; + // if not, deny access. + return accessMapForURI[aFeature]; + }, + + /** + * This function adds a new page to the access map, but can only + * be used in a test environment. + */ + addPage(aUrl, aFunctionMap) { + if (!Cu.isInAutomation) { + throw new Error("Cannot only modify privileges during testing"); + } + + if (aUrl in this.accessMap) { + throw new Error("Cannot modify privileges of existing page"); + } + + this.accessMap[aUrl] = aFunctionMap; + }, +}; |