Replace native elevator with sudo-prompt on windows

Changelog-entry: Replace native elevator with sudo-prompt on windows
Change-type: patch
This commit is contained in:
Alexis Svinartchouk 2020-08-04 18:33:25 +02:00
parent 140f3452ed
commit 281f119456
12 changed files with 9 additions and 459 deletions

View File

@ -17,7 +17,7 @@
"appId": "io.balena.etcher",
"copyright": "Copyright 2016-2020 Balena Ltd",
"productName": "balenaEtcher",
"nodeGypRebuild": true,
"nodeGypRebuild": false,
"afterPack": "./afterPack.js",
"asar": false,
"files": [

View File

@ -124,7 +124,6 @@ TARGETS = \
lint \
lint-ts \
lint-css \
lint-cpp \
lint-spell \
test-spectron \
test-gui \
@ -148,9 +147,6 @@ lint-ts:
lint-css:
npx prettier --write lib/**/*.css
lint-cpp:
cpplint --recursive src
lint-spell:
codespell \
--dictionary - \
@ -158,7 +154,7 @@ lint-spell:
--skip *.svg *.gz,*.bz2,*.xz,*.zip,*.img,*.dmg,*.iso,*.rpi-sdcard,*.wic,.DS_Store,*.dtb,*.dtbo,*.dat,*.elf,*.bin,*.foo,xz-without-extension \
lib tests docs Makefile *.md LICENSE
lint: lint-ts lint-css lint-cpp lint-spell
lint: lint-ts lint-css lint-spell
MOCHA_OPTIONS=--recursive --reporter spec --require ts-node/register --require-main "tests/gui/allow-renderer-process-reuse.ts"
@ -189,7 +185,6 @@ clean:
distclean: clean
rm -rf node_modules
rm -rf build
rm -rf dist
rm -rf generated
rm -rf $(BUILD_TEMPORARY_DIRECTORY)

View File

@ -1,35 +0,0 @@
{
"targets": [
{
"target_name": "elevator",
"include_dirs" : [
"src",
"<!(node -e \"require('nan')\")"
],
'conditions': [
[ 'OS=="win"', {
"sources": [
"src/utils/v8utils.cpp",
"src/os/win32/elevate.cpp",
"src/elevator_init.cpp",
],
"libraries": [
"-lShell32.lib",
],
} ],
[ 'OS=="mac"', {
"xcode_settings": {
"OTHER_CPLUSPLUSFLAGS": [
"-stdlib=libc++"
],
"OTHER_LDFLAGS": [
"-stdlib=libc++"
]
}
} ]
],
}
],
}

View File

@ -2,7 +2,7 @@ appId: io.balena.etcher
copyright: Copyright 2016-2020 Balena Ltd
productName: balenaEtcher
npmRebuild: true
nodeGypRebuild: true
nodeGypRebuild: false
publish: null
beforeBuild: "./beforeBuild.js"
afterPack: "./afterPack.js"

View File

@ -122,17 +122,12 @@ export function createLaunchScript(
async function elevateScriptWindows(
path: string,
): Promise<{ cancelled: boolean }> {
// 'elevator' imported here as it only exists on windows
// TODO: replace this with sudo-prompt once https://github.com/jorangreef/sudo-prompt/issues/96 is fixed
// @ts-ignore this is a native module
const { elevate } = await import('../../build/Release/elevator.node');
const elevateAsync = promisify(elevate);
name: string,
): Promise<{ cancelled: false }> {
// '&' needs to be escaped here (but not when written to a .cmd file)
const cmd = ['cmd', '/c', escapeParamCmd(path).replace(/&/g, '^&')];
const { cancelled } = await elevateAsync(cmd);
return { cancelled };
const cmd = ['cmd', '/c', escapeParamCmd(path).replace(/&/g, '^&')].join(' ');
await sudoExecAsync(cmd, { name });
return { cancelled: false };
}
async function elevateScriptUnix(
@ -183,7 +178,7 @@ export async function elevateCommand(
async (path) => {
await fs.writeFile(path, launchScript);
if (isWindows) {
return elevateScriptWindows(path);
return elevateScriptWindows(path, options.applicationName);
}
if (
os.platform() === 'darwin' &&

View File

@ -8,7 +8,6 @@
"description": "Flash OS images to SD cards and USB drives, safely and easily.",
"productDescription": "Etcher is a powerful OS image flasher built with web technologies to ensure flashing an SDCard or USB drive is a pleasant and safe experience. It protects you from accidentally writing to your hard-drives, ensures every byte of data was written correctly and much more.",
"homepage": "https://github.com/balena-io/etcher",
"gypfile": true,
"repository": {
"type": "git",
"url": "git@github.com:balena-io/etcher.git"
@ -17,9 +16,6 @@
"test": "make lint test sanity-checks",
"start": "./node_modules/.bin/electron .",
"postshrinkwrap": "ts-node ./scripts/clean-shrinkwrap.ts",
"configure": "node-gyp configure",
"build": "node-gyp build",
"install": "node-gyp rebuild",
"webpack": "webpack",
"watch": "webpack --watch",
"concourse-build-electron": "make webpack",
@ -85,7 +81,6 @@
"mocha": "^8.0.1",
"nan": "^2.14.0",
"native-addon-loader": "^2.0.1",
"node-gyp": "^7.0.0",
"node-ipc": "^9.1.1",
"omit-deep-lodash": "1.1.4",
"outdent": "^0.7.1",

View File

@ -1,4 +1,3 @@
codespell==1.12.0
cpplint==1.3.0
awscli==1.11.87
shyaml==0.5.0

View File

@ -1,83 +0,0 @@
/*
* Copyright 2017 balena.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <string>
#include <vector>
#include "os/elevate.h"
#include "utils/v8utils.h"
class ElevateWorker : public Nan::AsyncWorker {
public:
ElevateWorker(Nan::Callback *callback,
const std::vector<std::wstring> &arguments)
: Nan::AsyncWorker(callback) {
this->arguments = arguments;
}
~ElevateWorker() {}
void Execute() {
etcher::ELEVATE_RESULT result = etcher::Elevate(
this->arguments.front(),
std::vector<std::wstring>(this->arguments.begin() + 1,
this->arguments.end()));
switch (result) {
case etcher::ELEVATE_RESULT::ELEVATE_SUCCESS:
cancelled = false;
break;
case etcher::ELEVATE_RESULT::ELEVATE_CANCELLED:
cancelled = true;
break;
default:
this->SetErrorMessage(etcher::ElevateResultToString(result).c_str());
}
}
void HandleOKCallback() {
v8::Local<v8::Object> results = Nan::New<v8::Object>();
Nan::Set(results, Nan::New<v8::String>("cancelled").ToLocalChecked(),
this->cancelled ? Nan::True() : Nan::False());
v8::Local<v8::Value> argv[2] = { Nan::Null(), results };
callback->Call(2, argv);
}
private:
std::vector<std::wstring> arguments;
v8::Local<v8::Object> results;
bool cancelled;
};
NAN_METHOD(elevate) {
if (!info[0]->IsArray()) {
return Nan::ThrowError("This function expects an array");
}
if (!info[1]->IsFunction()) {
return Nan::ThrowError("Callback must be a function");
}
std::vector<std::wstring> arguments =
etcher::v8utils::GetArguments(info[0].As<v8::Array>());
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
Nan::AsyncQueueWorker(new ElevateWorker(callback, arguments));
info.GetReturnValue().SetUndefined();
}
NAN_MODULE_INIT(ElevatorInit) { NAN_EXPORT(target, elevate); }
NODE_MODULE(elevator, ElevatorInit)

View File

@ -1,63 +0,0 @@
#ifndef SRC_OS_ELEVATE_H_
#define SRC_OS_ELEVATE_H_
/*
* Copyright 2017 balena.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef _WIN32
// Fix winsock.h redefinition errors
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
// Note that windows.h has to be included before any
// other Windows library to avoid declaration issues
#include <windows.h>
#include <shellapi.h>
#endif
#include <algorithm>
#include <iterator>
#include <sstream>
#include <string>
#include <vector>
namespace etcher {
enum class ELEVATE_RESULT {
ELEVATE_SUCCESS,
ELEVATE_FILE_NOT_FOUND,
ELEVATE_PATH_NOT_FOUND,
ELEVATE_DDE_FAIL,
ELEVATE_NO_ASSOCIATION,
ELEVATE_ACCESS_DENIED,
ELEVATE_DLL_NOT_FOUND,
ELEVATE_CANCELLED,
ELEVATE_NOT_ENOUGH_MEMORY,
ELEVATE_SHARING_VIOLATION,
ELEVATE_UNKNOWN_ERROR
};
ELEVATE_RESULT Elevate(const std::wstring &command,
std::vector<std::wstring> arguments);
std::string ElevateResultToString(const ELEVATE_RESULT &result);
} // namespace etcher
#endif // SRC_OS_ELEVATE_H_

View File

@ -1,158 +0,0 @@
/*
* Copyright 2017 balena.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "os/elevate.h"
static std::wstring JoinArguments(std::vector<std::wstring> arguments) {
std::wostringstream result;
std::copy(arguments.begin(), arguments.end(),
std::ostream_iterator<std::wstring, wchar_t>(result, L" "));
return result.str();
}
// Make sure to delete the result after you're done
// with it by calling `delete[] result;`.
// See http://stackoverflow.com/a/1201471
static LPCWSTR ConvertStringToLPCWSTR(const std::wstring &string) {
wchar_t *result = new wchar_t[string.size() + 1];
std::copy(string.begin(), string.end(), result);
result[string.size()] = 0;
return result;
}
etcher::ELEVATE_RESULT etcher::Elevate(const std::wstring &command,
std::vector<std::wstring> arguments) {
// Initialize the SHELLEXECUTEINFO structure. We zero it out
// in order to be on the safe side, and set cbSize to the size
// of the structure as recommend by MSDN
// See: https://msdn.microsoft.com/en-us/library/windows/desktop/bb759784(v=vs.85).aspx
SHELLEXECUTEINFOW shellExecuteInfo;
ZeroMemory(&shellExecuteInfo, sizeof(shellExecuteInfo));
shellExecuteInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
// Flags that indicate the content and validity of the other structure member.
shellExecuteInfo.fMask =
// Used to indicate that the hProcess member receives the process handle.
// This handle is typically used to allow an application to find out
// when a process created with ShellExecuteEx terminates.
SEE_MASK_NOCLOSEPROCESS |
// Wait for the execute operation to complete before returning.
SEE_MASK_NOASYNC |
// Do not display an error message box if an error occurs.
SEE_MASK_FLAG_NO_UI;
// The action to be performed.
shellExecuteInfo.lpVerb = L"runas";
// Run the file in the background
shellExecuteInfo.nShow = SW_HIDE;
// Use the current directory as the working directory
shellExecuteInfo.lpDirectory = NULL;
// Set file and parameters
// We can't just assign the result of `.c_str()`, since
// that pointer is owned by the `std::wstring` instance,
// and will not be safe after the instance is destroyed.
LPCWSTR file = ConvertStringToLPCWSTR(command);
LPCWSTR argv = ConvertStringToLPCWSTR(JoinArguments(arguments));
shellExecuteInfo.lpFile = file;
shellExecuteInfo.lpParameters = argv;
BOOL executeResult = ShellExecuteExW(&shellExecuteInfo);
delete[] file;
delete[] argv;
// Finally, let's try to elevate the command
if (!executeResult) {
DWORD executeError = GetLastError();
// We map Windows error codes to our own enum class
// so we can normalize all Windows error handling mechanisms.
switch (executeError) {
case ERROR_FILE_NOT_FOUND:
return etcher::ELEVATE_RESULT::ELEVATE_FILE_NOT_FOUND;
case ERROR_PATH_NOT_FOUND:
return etcher::ELEVATE_RESULT::ELEVATE_PATH_NOT_FOUND;
case ERROR_DDE_FAIL:
return etcher::ELEVATE_RESULT::ELEVATE_DDE_FAIL;
case ERROR_NO_ASSOCIATION:
return etcher::ELEVATE_RESULT::ELEVATE_NO_ASSOCIATION;
case ERROR_ACCESS_DENIED:
return etcher::ELEVATE_RESULT::ELEVATE_ACCESS_DENIED;
case ERROR_DLL_NOT_FOUND:
return etcher::ELEVATE_RESULT::ELEVATE_DLL_NOT_FOUND;
case ERROR_CANCELLED:
return etcher::ELEVATE_RESULT::ELEVATE_CANCELLED;
case ERROR_NOT_ENOUGH_MEMORY:
return etcher::ELEVATE_RESULT::ELEVATE_NOT_ENOUGH_MEMORY;
case ERROR_SHARING_VIOLATION:
return etcher::ELEVATE_RESULT::ELEVATE_SHARING_VIOLATION;
default:
return etcher::ELEVATE_RESULT::ELEVATE_UNKNOWN_ERROR;
}
}
// Since we passed SEE_MASK_NOCLOSEPROCESS, the
// process handle is accessible from hProcess.
if (shellExecuteInfo.hProcess) {
// Wait for the process to exit before continuing.
// See: https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx
WaitForSingleObject(shellExecuteInfo.hProcess, INFINITE);
if (!CloseHandle(shellExecuteInfo.hProcess)) {
return etcher::ELEVATE_RESULT::ELEVATE_UNKNOWN_ERROR;
}
}
return etcher::ELEVATE_RESULT::ELEVATE_SUCCESS;
}
std::string
etcher::ElevateResultToString(const etcher::ELEVATE_RESULT &result) {
switch (result) {
case etcher::ELEVATE_RESULT::ELEVATE_SUCCESS:
return "Success";
case etcher::ELEVATE_RESULT::ELEVATE_CANCELLED:
return "The user cancelled the elevation request";
case etcher::ELEVATE_RESULT::ELEVATE_FILE_NOT_FOUND:
return "The specified file was not found";
case etcher::ELEVATE_RESULT::ELEVATE_PATH_NOT_FOUND:
return "The specified path was not found";
case etcher::ELEVATE_RESULT::ELEVATE_DDE_FAIL:
return "The Dynamic Data Exchange (DDE) transaction failed";
case etcher::ELEVATE_RESULT::ELEVATE_NO_ASSOCIATION:
return "There is no application associated with the "
"specified file name extension";
case etcher::ELEVATE_RESULT::ELEVATE_ACCESS_DENIED:
return "Access to the specified file is denied";
case etcher::ELEVATE_RESULT::ELEVATE_DLL_NOT_FOUND:
return "One of the library files necessary to run the "
"application can't be found";
case etcher::ELEVATE_RESULT::ELEVATE_NOT_ENOUGH_MEMORY:
return "There is not enough memory to perform the specified action";
case etcher::ELEVATE_RESULT::ELEVATE_SHARING_VIOLATION:
return "A sharing violation occurred";
default:
return "Unknown error";
}
}

View File

@ -1,36 +0,0 @@
/*
* Copyright 2017 balena.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "utils/v8utils.h"
std::vector<std::wstring>
etcher::v8utils::GetArguments(v8::Local<v8::Array> arguments) {
std::vector<std::wstring> result(0);
for (uint32_t index = 0; index < arguments->Length(); index++) {
// See https://stackoverflow.com/q/15615136/1641422
std::string stringArgument(
*Nan::Utf8String(
arguments->Get(
Nan::GetCurrentContext(),
index).ToLocalChecked()));
std::wstring_convert<std::codecvt_utf8<wchar_t>> conversion;
result.push_back(conversion.from_bytes(stringArgument));
}
return result;
}

View File

@ -1,59 +0,0 @@
#ifndef SRC_UTILS_V8UTILS_H_
#define SRC_UTILS_V8UTILS_H_
/*
* Copyright 2017 balena.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <nan.h>
#include <string>
#include <vector>
#include <codecvt>
namespace etcher {
namespace v8utils {
std::vector<std::wstring> GetArguments(v8::Local<v8::Array> arguments);
} // namespace v8utils
} // namespace etcher
#define YIELD_ERROR(CALLBACK, ERROR) \
{ \
const wchar_t *message = (ERROR).c_str(); \
v8::Local<v8::Value> argv[1] = { \
Nan::Error(v8::String::NewFromTwoByte(isolate, \
(const uint16_t *)message)) \
}; \
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (CALLBACK), \
1, argv); \
} \
return;
#define YIELD_OBJECT(CALLBACK, OBJECT) \
{ \
v8::Local<v8::Value> argv[2] = {Nan::Null(), (OBJECT)}; \
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (CALLBACK), 2, \
argv); \
} \
return;
#define YIELD_NOTHING(CALLBACK) \
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (CALLBACK), 0, 0);
#define NAN_SET_FUNCTION(JSSYMBOL, FUNCTION) \
Nan::Set(target, Nan::New((JSSYMBOL)).ToLocalChecked(), \
Nan::GetFunction(Nan::New<v8::FunctionTemplate>((FUNCTION))) \
.ToLocalChecked());
#endif // SRC_UTILS_V8UTILS_H_