diff --git a/install/FirefoxAction.CA.dll b/install/FirefoxAction.CA.dll deleted file mode 100644 index e185b9fd..00000000 Binary files a/install/FirefoxAction.CA.dll and /dev/null differ diff --git a/install/firefox-action/.gitignore b/install/firefox-action/.gitignore new file mode 100644 index 00000000..6ada809d --- /dev/null +++ b/install/firefox-action/.gitignore @@ -0,0 +1,4 @@ +bin/ +obj/ +packages/ +.vs/ diff --git a/install/firefox-action/CustomAction.config b/install/firefox-action/CustomAction.config new file mode 100644 index 00000000..acae396c --- /dev/null +++ b/install/firefox-action/CustomAction.config @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + diff --git a/install/firefox-action/FirefoxAction.cs b/install/firefox-action/FirefoxAction.cs new file mode 100644 index 00000000..3e12e415 --- /dev/null +++ b/install/firefox-action/FirefoxAction.cs @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2019-2024 Estonian Information System Authority + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +using Microsoft.Win32; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Linq; +using WixToolset.Dtf.WindowsInstaller; + +namespace FirefoxAction +{ + public class FirefoxActions + { + const string KEY_NAME = "ExtensionSettings"; + + [CustomAction] + public static ActionResult ExtensionSettingsInstall(Session session) + { + // Instead of catching and logging errors and returning ActionResult.Failure, + // we simply let all exceptions through. In both cases the installation fails + // with error status 1603, but when letting exceptions through the raw exception + // stack trace will be logged in installer log which gives more information. + + var extensionSettings = session.GetExtensionSettings(); + session.Log("Begin ExtensionSettingsInstall " + extensionSettings.UUID); + using (RegistryKey firefox = Utils.FirefoxKey()) + { + var json = firefox.GetJSON(KEY_NAME, "{}"); + json[extensionSettings.UUID] = new JObject + { + ["installation_mode"] = "normal_installed", + ["install_url"] = extensionSettings.URL + }; + firefox.SetValue(KEY_NAME, json.ToString().Split('\n')); + return ActionResult.Success; + } + } + + [CustomAction] + public static ActionResult ExtensionSettingsRemove(Session session) + { + var extensionSettings = session.GetExtensionSettings(); + session.Log("Begin ExtensionSettingsRemove " + extensionSettings.UUID); + using (RegistryKey firefox = Utils.FirefoxKey()) + { + var json = firefox.GetJSON(KEY_NAME); + if (json != null) + { + json[extensionSettings.UUID] = new JObject + { + ["installation_mode"] = "blocked" + }; + firefox.SetValue(KEY_NAME, json.ToString().Split('\n')); + } + } + return ActionResult.Success; + } + } + + internal static class Utils + { + internal static (string UUID, string URL) GetExtensionSettings(this Session session) + { + // Deferred custom actions cannot directly access installer properties from session, + // only the CustomActionData property is available, see README how to populate it. + return ( + session.CustomActionData["EXTENSIONSETTINGS_UUID"], + session.CustomActionData["EXTENSIONSETTINGS_URL"] + ); + } + + internal static RegistryKey FirefoxKey() + { + using (RegistryKey mozilla = Registry.LocalMachine.OpenOrCreateSubKey(@"Software\Policies\Mozilla", true)) + { + return mozilla.OpenOrCreateSubKey(@"Firefox", true); + } + } + + internal static RegistryKey OpenOrCreateSubKey(this RegistryKey registryKey, string name, bool writable = false) + { + return registryKey.OpenSubKey(name, writable) ?? registryKey.CreateSubKey(name); + } + + internal static string GetStringValue(this RegistryKey registryKey, string name, string defaultValue = null) + { + if (!registryKey.GetValueNames().Any(name.Equals)) + return defaultValue; + switch (registryKey.GetValueKind(name)) + { + case RegistryValueKind.String: + case RegistryValueKind.ExpandString: + return (string)registryKey.GetValue(name, defaultValue); + case RegistryValueKind.MultiString: + return string.Join("\n", (string[])registryKey.GetValue(name)); + default: return defaultValue; + } + } + + internal static JObject GetJSON(this RegistryKey registryKey, string name, string defaultValue = null) + { + string value = registryKey.GetStringValue(name, defaultValue); + if (value == null) + { + return null; + } + try + { + return JObject.Parse(value); + } + catch (JsonReaderException) + { + return new JObject(); + } + } + } +} diff --git a/install/firefox-action/FirefoxAction.csproj b/install/firefox-action/FirefoxAction.csproj new file mode 100644 index 00000000..cdded0dd --- /dev/null +++ b/install/firefox-action/FirefoxAction.csproj @@ -0,0 +1,17 @@ + + + net40 + Copyright © 2019 + RIA + README.md + LICENSE + + + + + + + + + + \ No newline at end of file diff --git a/install/firefox-action/LICENSE b/install/firefox-action/LICENSE new file mode 100644 index 00000000..ebbc5e6a --- /dev/null +++ b/install/firefox-action/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021-2023 Estonian Information System Authority + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/install/firefox-action/README.md b/install/firefox-action/README.md new file mode 100644 index 00000000..6dacc498 --- /dev/null +++ b/install/firefox-action/README.md @@ -0,0 +1,36 @@ +# WiX custom action for installing a Firefox extension with enterprise policy + +*FirefoxAction* is a custom action for WiX that installs the given Firefox +extension with the Firefox enterprise policy engine. + +The custom action should be added to a WiX project as follows, it must be deferred +and not use impersonation (i.e. run in privileged mode for registry access): + + + + + +The Firefox extension UUID and AMO URL must be passed in via `CustomSettingsData` properties via +dedicated custom actions as follows: + + + + +The custom actions must be scheduled in `InstallExecuteSequence` as follows: + + + + + NOT REMOVE="ALL" + + + + REMOVE="ALL" AND NOT UPGRADINGPRODUCTCODE + + + +The code has been forked from the [OpenEID Windows installer project](https://github.com/open-eid/windows-installer/blob/master/FirefoxActionWix/). diff --git a/install/web-eid.wxs b/install/web-eid.wxs index 8e45aa14..08d59f2c 100644 --- a/install/web-eid.wxs +++ b/install/web-eid.wxs @@ -37,10 +37,8 @@ - - + + _${PROJECT_VERSION}.$ENV{PLATFORM}) + find_program(MSBUILD msbuild) + set(FIREFOX_ACTION_DLL ${CMAKE_CURRENT_BINARY_DIR}/FirefoxAction.CA.dll) + add_custom_command( + OUTPUT ${FIREFOX_ACTION_DLL} + COMMAND ${MSBUILD} ${CMAKE_SOURCE_DIR}/install/firefox-action/FirefoxAction.csproj + /restore /t:rebuild /property:Configuration=Release + "/property:OutDir=${CMAKE_CURRENT_BINARY_DIR}/" + DEPENDS + ${CMAKE_SOURCE_DIR}/install/firefox-action/FirefoxAction.cs + ${CMAKE_SOURCE_DIR}/install/firefox-action/FirefoxAction.csproj + ${CMAKE_SOURCE_DIR}/install/firefox-action/CustomAction.config + COMMENT "Building Firefox custom action DLL" + ) + add_custom_target(firefox-action DEPENDS ${FIREFOX_ACTION_DLL}) set(WIX_CMD wix.exe build -nologo -arch $ENV{PLATFORM} -ext WixToolset.UI.wixext @@ -32,12 +46,13 @@ if(WIN32) -d jsonfirefox=${CMAKE_CURRENT_BINARY_DIR}/eu.webeid.firefox.json -d FIREFOX_URL="${FIREFOX_URL}" -d FIREFOX_UUID="${FIREFOX_UUID}" + -d FirefoxActionDll=${FIREFOX_ACTION_DLL} -d app_path=$ ${CMAKE_SOURCE_DIR}/install/web-eid.wxs ${CMAKE_SOURCE_DIR}/install/WelcomeDlg.wxs ${CMAKE_SOURCE_DIR}/install/WixUI_Minimal.wxs ) - add_custom_target(installer DEPENDS web-eid + add_custom_target(installer DEPENDS web-eid firefox-action COMMAND ${WIX_CMD} -o "${BASE_FILE}.msi" #Build MSI with QT COMMAND ${WIX_CMD} -d qt_path=${Qt6_DIR}/../../../bin -o "${BASE_FILE}.qt.msi" @@ -50,6 +65,9 @@ if(WIN32) COMMAND ${SIGNCMD} "$<$:/ph;/ac;${CROSSSIGNCERT}>" $ COMMAND_EXPAND_LISTS ) + add_custom_command(TARGET firefox-action POST_BUILD + COMMAND ${SIGNCMD} ${FIREFOX_ACTION_DLL} + ) add_custom_command(TARGET installer POST_BUILD COMMAND ${SIGNCMD} "${BASE_FILE}.msi" "${BASE_FILE}.qt.msi" WORKING_DIRECTORY $