Add `userSettings` entry to managed storage

The managed `userSettings` entry is an array of entries,
where each entry is a name/value pair encoded into an array
of strings.

The first item in the entry array is the name of a setting,
and the second item is the stringified value for the
setting.

This is a more convenient way for administrators to set
specific user settings. The settings set through
`userSettings` policy will always be set at uBO launch
time.
This commit is contained in:
Raymond Hill 2021-01-16 10:35:56 -05:00
parent 2f4952e769
commit 6eb1246508
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
6 changed files with 149 additions and 60 deletions

View File

@ -16,6 +16,15 @@
"items": { "type": "string" }
}
},
"userSettings": {
"title": "A list of [name,value] pairs to populate user settings",
"type": "array",
"items": {
"title": "A [name,value] pair",
"type": "array",
"items": { "type": "string" }
}
},
"disableDashboard": {
"title": "Set to true to prevent access to configuration options",
"type": "boolean"

View File

@ -83,29 +83,32 @@ const µBlock = (( ) => { // jshint ignore:line
userResourcesLocation: 'unset',
};
const userSettingsDefault = {
advancedUserEnabled: false,
alwaysDetachLogger: true,
autoUpdate: true,
cloudStorageEnabled: false,
collapseBlocked: true,
colorBlindFriendly: false,
contextMenuEnabled: true,
dynamicFilteringEnabled: false,
externalLists: [],
firewallPaneMinimized: true,
hyperlinkAuditingDisabled: true,
ignoreGenericCosmeticFilters: vAPI.webextFlavor.soup.has('mobile'),
largeMediaSize: 50,
parseAllABPHideFilters: true,
popupPanelSections: 0b111,
prefetchingDisabled: true,
requestLogMaxEntries: 1000,
showIconBadge: true,
tooltipsDisabled: false,
webrtcIPAddressHidden: false,
};
return {
userSettings: {
advancedUserEnabled: false,
alwaysDetachLogger: true,
autoUpdate: true,
cloudStorageEnabled: false,
collapseBlocked: true,
colorBlindFriendly: false,
contextMenuEnabled: true,
dynamicFilteringEnabled: false,
externalLists: [],
firewallPaneMinimized: true,
hyperlinkAuditingDisabled: true,
ignoreGenericCosmeticFilters: vAPI.webextFlavor.soup.has('mobile'),
largeMediaSize: 50,
parseAllABPHideFilters: true,
popupPanelSections: 0b111,
prefetchingDisabled: true,
requestLogMaxEntries: 1000,
showIconBadge: true,
tooltipsDisabled: false,
webrtcIPAddressHidden: false,
},
userSettingsDefault: userSettingsDefault,
userSettings: Object.assign({}, userSettingsDefault),
hiddenSettingsDefault: hiddenSettingsDefault,
hiddenSettingsAdmin: {},

View File

@ -912,12 +912,12 @@ const backupUserData = async function() {
const userData = {
timeStamp: Date.now(),
version: vAPI.app.version,
userSettings: µb.userSettings,
userSettings:
µb.getModifiedSettings(µb.userSettings, µb.userSettingsDefault),
selectedFilterLists: µb.selectedFilterLists,
hiddenSettings: µb.getModifiedHiddenSettings(),
hiddenSettings:
µb.getModifiedSettings(µb.hiddenSettings, µb.hiddenSettingsDefault),
whitelist: µb.arrayFromWhitelist(µb.netWhitelist),
// String representation eventually to be deprecated
netWhitelist: µb.stringFromWhitelist(µb.netWhitelist),
dynamicFilteringString: µb.permanentFirewall.toString(),
urlFilteringString: µb.permanentURLFiltering.toString(),
hostnameSwitchesString: µb.permanentSwitches.toString(),

View File

@ -160,30 +160,21 @@ const onNetWhitelistReady = function(netWhitelistRaw, adminExtra) {
// User settings are in memory
const onUserSettingsReady = function(fetched) {
const userSettings = µb.userSettings;
// List of external lists is meant to be an array
if ( typeof fetched.externalLists === 'string' ) {
fetched.externalLists =
fetched.externalLists.trim().split(/[\n\r]+/);
}
fromFetch(userSettings, fetched);
fromFetch(µb.userSettings, fetched);
if ( µb.privacySettingsSupported ) {
vAPI.browserSettings.set({
'hyperlinkAuditing': !userSettings.hyperlinkAuditingDisabled,
'prefetching': !userSettings.prefetchingDisabled,
'webrtcIPAddress': !userSettings.webrtcIPAddressHidden
'hyperlinkAuditing': !µb.userSettings.hyperlinkAuditingDisabled,
'prefetching': !µb.userSettings.prefetchingDisabled,
'webrtcIPAddress': !µb.userSettings.webrtcIPAddressHidden
});
}
µb.permanentFirewall.fromString(fetched.dynamicFilteringString);
µb.sessionFirewall.assign(µb.permanentFirewall);
µb.permanentURLFiltering.fromString(fetched.urlFilteringString);
µb.sessionURLFiltering.assign(µb.permanentURLFiltering);
µb.permanentSwitches.fromString(fetched.hostnameSwitchesString);
µb.sessionSwitches.assign(µb.permanentSwitches);
};
/******************************************************************************/
@ -219,8 +210,15 @@ const onFirstFetchReady = function(fetched, adminExtra) {
// Order is important -- do not change:
fromFetch(µb.localSettings, fetched);
onUserSettingsReady(fetched);
fromFetch(µb.restoreBackupSettings, fetched);
µb.permanentFirewall.fromString(fetched.dynamicFilteringString);
µb.sessionFirewall.assign(µb.permanentFirewall);
µb.permanentURLFiltering.fromString(fetched.urlFilteringString);
µb.sessionURLFiltering.assign(µb.permanentURLFiltering);
µb.permanentSwitches.fromString(fetched.hostnameSwitchesString);
µb.sessionSwitches.assign(µb.permanentSwitches);
onNetWhitelistReady(fetched.netWhitelist, adminExtra);
onVersionReady(fetched.version);
};
@ -269,7 +267,6 @@ const createDefaultProps = function() {
fetchableProps.hostnameSwitchesString += '\nno-csp-reports: * true';
}
toFetch(µb.localSettings, fetchableProps);
toFetch(µb.userSettings, fetchableProps);
toFetch(µb.restoreBackupSettings, fetchableProps);
return fetchableProps;
};
@ -321,6 +318,10 @@ try {
log.info(`First fetch ready ${Date.now()-vAPI.T0} ms after launch`);
onFirstFetchReady(fetched, adminExtra);
}),
µb.loadUserSettings().then(fetched => {
log.info(`User settings ready ${Date.now()-vAPI.T0} ms after launch`);
onUserSettingsReady(fetched);
}),
µb.loadPublicSuffixList().then(( ) => {
log.info(`PSL ready ${Date.now()-vAPI.T0} ms after launch`);
}),

View File

@ -83,8 +83,49 @@
/******************************************************************************/
µBlock.loadUserSettings = async function() {
const usDefault = this.userSettingsDefault;
const results = await Promise.all([
vAPI.storage.get(Object.assign(usDefault)),
vAPI.adminStorage.get('userSettings'),
]);
const usUser = results[0] instanceof Object && results[0] ||
Object.assign(usDefault);
if ( Array.isArray(results[1]) ) {
const adminSettings = results[1];
for ( const entry of adminSettings ) {
if ( entry.length < 1 ) { continue; }
const name = entry[0];
if ( usDefault.hasOwnProperty(name) === false ) { continue; }
const value = entry.length < 2
? usDefault[name]
: this.settingValueFromString(usDefault, name, entry[1]);
if ( value === undefined ) { continue; }
usUser[name] = usDefault[name] = value;
}
}
return usUser;
};
µBlock.saveUserSettings = function() {
vAPI.storage.set(this.userSettings);
const toSave = this.getModifiedSettings(
this.userSettings,
this.userSettingsDefault
);
const toRemove = [];
for ( const key in this.userSettings ) {
if ( this.userSettings.hasOwnProperty(key) === false ) { continue; }
if ( toSave.hasOwnProperty(key) === false ) { continue; }
toRemove.push(key);
}
if ( toRemove.length !== 0 ) {
vAPI.storage.remove(toRemove);
}
vAPI.storage.set(toSave);
};
/******************************************************************************/
@ -164,24 +205,16 @@
};
// Note: Save only the settings which values differ from the default ones.
// This way the new default values in the future will properly apply for those
// which were not modified by the user.
µBlock.getModifiedHiddenSettings = function() {
const out = {};
for ( const prop in this.hiddenSettings ) {
if (
this.hiddenSettings.hasOwnProperty(prop) &&
this.hiddenSettings[prop] !== this.hiddenSettingsDefault[prop]
) {
out[prop] = this.hiddenSettings[prop];
}
}
return out;
};
// This way the new default values in the future will properly apply for
// those which were not modified by the user.
µBlock.saveHiddenSettings = function() {
vAPI.storage.set({ hiddenSettings: this.getModifiedHiddenSettings() });
vAPI.storage.set({
hiddenSettings: this.getModifiedSettings(
this.hiddenSettings,
this.hiddenSettingsDefault
)
});
};
self.addEventListener('hiddenSettingsChanged', ( ) => {
@ -404,7 +437,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
const result = Array.from(selectedListKeySet);
if ( externalLists.join() !== this.userSettings.externalLists.join() ) {
this.userSettings.externalLists = externalLists;
vAPI.storage.set({ externalLists });
this.saveUserSettings();
}
this.saveSelectedFilterLists(result);
};
@ -599,7 +632,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => {
this.assets.registerAssetSource(listURL, newEntry);
importedListKeys.push(listURL);
this.userSettings.externalLists.push(listURL.trim());
vAPI.storage.set({ externalLists: this.userSettings.externalLists });
this.saveUserSettings();
this.saveSelectedFilterLists([ listURL ], true);
};

View File

@ -684,3 +684,46 @@
window.dispatchEvent(new CustomEvent(name));
}
};
/******************************************************************************/
µBlock.getModifiedSettings = function(edit, orig = {}) {
const out = {};
for ( const prop in edit ) {
if ( orig.hasOwnProperty(prop) && edit[prop] !== orig[prop] ) {
out[prop] = edit[prop];
}
}
return out;
};
µBlock.settingValueFromString = function(orig, name, s) {
if ( typeof name !== 'string' || typeof s !== 'string' ) { return; }
if ( orig.hasOwnProperty(name) === false ) { return; }
let r;
switch ( typeof orig[name] ) {
case 'boolean':
if ( s === 'true' ) {
r = true;
} else if ( s === 'false' ) {
r = false;
}
break;
case 'string':
r = s.trim();
break;
case 'number':
if ( s.startsWith('0b') ) {
r = parseInt(s.slice(2), 2);
} else if ( s.startsWith('0x') ) {
r = parseInt(s.slice(2), 16);
} else {
r = parseInt(s, 10);
}
if ( isNaN(r) ) { r = undefined; }
break;
default:
break;
}
return r;
};