This is everything from dev/migrie/f/non-terminal-content-elevation-warning for specifically ElevatedState
This commit is contained in:
parent
c2fcdcbe10
commit
97b0f06504
896
patch.diff
Normal file
896
patch.diff
Normal file
|
@ -0,0 +1,896 @@
|
|||
diff --git a/src/cascadia/LocalTests_SettingsModel/pch.h b/src/cascadia/LocalTests_SettingsModel/pch.h
|
||||
index 53f6145d2..01b4cdfe8 100644
|
||||
--- a/src/cascadia/LocalTests_SettingsModel/pch.h
|
||||
+++ b/src/cascadia/LocalTests_SettingsModel/pch.h
|
||||
@@ -61,6 +61,9 @@ Author(s):
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#include "til.h"
|
||||
|
||||
+#include <til/mutex.h>
|
||||
+#include <til/throttled_func.h>
|
||||
+
|
||||
// Common includes for most tests:
|
||||
#include "../../inc/argb.h"
|
||||
#include "../../inc/conattrs.hpp"
|
||||
diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp
|
||||
index 6d53a6ba7..38edbc2c9 100644
|
||||
--- a/src/cascadia/TerminalApp/AppLogic.cpp
|
||||
+++ b/src/cascadia/TerminalApp/AppLogic.cpp
|
||||
@@ -189,7 +210,7 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
|
||||
AppLogic::AppLogic() :
|
||||
- _reloadState{ std::chrono::milliseconds(100), []() { ApplicationState::SharedInstance().Reload(); } }
|
||||
+ _reloadState{ std::chrono::milliseconds(100), []() { ApplicationState::SharedInstance().Reload(); ElevatedState::SharedInstance().Reload(); } }
|
||||
{
|
||||
// For your own sanity, it's better to do setup outside the ctor.
|
||||
// If you do any setup in the ctor that ends up throwing an exception,
|
||||
@@ -906,8 +927,6 @@ namespace winrt::TerminalApp::implementation
|
||||
void AppLogic::_RegisterSettingsChange()
|
||||
{
|
||||
const std::filesystem::path settingsPath{ std::wstring_view{ CascadiaSettings::SettingsPath() } };
|
||||
- const std::filesystem::path statePath{ std::wstring_view{ ApplicationState::SharedInstance().FilePath() } };
|
||||
-
|
||||
_reader.create(
|
||||
settingsPath.parent_path().c_str(),
|
||||
false,
|
||||
@@ -916,14 +935,17 @@ namespace winrt::TerminalApp::implementation
|
||||
// editors, who will write a temp file, then rename it to be the
|
||||
// actual file you wrote. So listen for that too.
|
||||
wil::FolderChangeEvents::FileName | wil::FolderChangeEvents::LastWriteTime,
|
||||
- [this, settingsBasename = settingsPath.filename(), stateBasename = statePath.filename()](wil::FolderChangeEvent, PCWSTR fileModified) {
|
||||
+ [this, settingsPath](wil::FolderChangeEvent, PCWSTR fileModified) {
|
||||
+ static const std::filesystem::path statePath{ std::wstring_view{ ApplicationState::SharedInstance().FilePath() } };
|
||||
+ static const std::filesystem::path elevatedStatePath{ std::wstring_view{ ElevatedState::SharedInstance().FilePath() } };
|
||||
+
|
||||
const auto modifiedBasename = std::filesystem::path{ fileModified }.filename();
|
||||
|
||||
- if (modifiedBasename == settingsBasename)
|
||||
+ if (modifiedBasename == settingsPath.filename())
|
||||
{
|
||||
_reloadSettings->Run();
|
||||
}
|
||||
- else if (modifiedBasename == stateBasename)
|
||||
+ else if (modifiedBasename == statePath.filename() || modifiedBasename == elevatedStatePath.filename())
|
||||
{
|
||||
_reloadState();
|
||||
}
|
||||
diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp
|
||||
index 5a00ba2b4..ec7f5cd7a 100644
|
||||
--- a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp
|
||||
+++ b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp
|
||||
@@ -60,38 +60,40 @@ using namespace ::Microsoft::Terminal::Settings::Model;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
+ ApplicationState::ApplicationState(std::filesystem::path path) noexcept :
|
||||
+ BaseApplicationState{ path } {}
|
||||
+
|
||||
// Returns the application-global ApplicationState object.
|
||||
Microsoft::Terminal::Settings::Model::ApplicationState ApplicationState::SharedInstance()
|
||||
{
|
||||
static auto state = winrt::make_self<ApplicationState>(GetBaseSettingsPath() / stateFileName);
|
||||
+ state->Reload();
|
||||
return *state;
|
||||
}
|
||||
|
||||
- ApplicationState::ApplicationState(std::filesystem::path path) noexcept :
|
||||
- _path{ std::move(path) },
|
||||
- _throttler{ std::chrono::seconds(1), [this]() { _write(); } }
|
||||
+ void ApplicationState::FromJson(const Json::Value& root) const noexcept
|
||||
{
|
||||
- _read();
|
||||
- }
|
||||
-
|
||||
- // The destructor ensures that the last write is flushed to disk before returning.
|
||||
- ApplicationState::~ApplicationState()
|
||||
- {
|
||||
- // This will ensure that we not just cancel the last outstanding timer,
|
||||
- // but instead force it to run as soon as possible and wait for it to complete.
|
||||
- _throttler.flush();
|
||||
+ auto state = _state.lock();
|
||||
+ // GetValueForKey() comes in two variants:
|
||||
+ // * take a std::optional<T> reference
|
||||
+ // * return std::optional<T> by value
|
||||
+ // At the time of writing the former version skips missing fields in the json,
|
||||
+ // but we want to explicitly clear state fields that were removed from state.json.
|
||||
+#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) state->name = JsonUtils::GetValueForKey<std::optional<type>>(root, key);
|
||||
+ MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
+#undef MTSM_APPLICATION_STATE_GEN
|
||||
}
|
||||
-
|
||||
- // Re-read the state.json from disk.
|
||||
- void ApplicationState::Reload() const noexcept
|
||||
+ Json::Value ApplicationState::ToJson() const noexcept
|
||||
{
|
||||
- _read();
|
||||
- }
|
||||
+ Json::Value root{ Json::objectValue };
|
||||
|
||||
- // Returns the state.json path on the disk.
|
||||
- winrt::hstring ApplicationState::FilePath() const noexcept
|
||||
- {
|
||||
- return winrt::hstring{ _path.wstring() };
|
||||
+ {
|
||||
+ auto state = _state.lock_shared();
|
||||
+#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) JsonUtils::SetValueForKey(root, key, state->name);
|
||||
+ MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
+#undef MTSM_APPLICATION_STATE_GEN
|
||||
+ }
|
||||
+ return root;
|
||||
}
|
||||
|
||||
// Generate all getter/setters
|
||||
@@ -115,58 +117,4 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
|
||||
- // Deserializes the state.json at _path into this ApplicationState.
|
||||
- // * ANY errors during app state will result in the creation of a new empty state.
|
||||
- // * ANY errors during runtime will result in changes being partially ignored.
|
||||
- void ApplicationState::_read() const noexcept
|
||||
- try
|
||||
- {
|
||||
- const auto data = ReadUTF8FileIfExists(_path).value_or(std::string{});
|
||||
- if (data.empty())
|
||||
- {
|
||||
- return;
|
||||
- }
|
||||
-
|
||||
- std::string errs;
|
||||
- std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
|
||||
-
|
||||
- Json::Value root;
|
||||
- if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs))
|
||||
- {
|
||||
- throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
|
||||
- }
|
||||
-
|
||||
- auto state = _state.lock();
|
||||
- // GetValueForKey() comes in two variants:
|
||||
- // * take a std::optional<T> reference
|
||||
- // * return std::optional<T> by value
|
||||
- // At the time of writing the former version skips missing fields in the json,
|
||||
- // but we want to explicitly clear state fields that were removed from state.json.
|
||||
-#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) state->name = JsonUtils::GetValueForKey<std::optional<type>>(root, key);
|
||||
- MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
-#undef MTSM_APPLICATION_STATE_GEN
|
||||
- }
|
||||
- CATCH_LOG()
|
||||
-
|
||||
- // Serialized this ApplicationState (in `context`) into the state.json at _path.
|
||||
- // * Errors are only logged.
|
||||
- // * _state->_writeScheduled is set to false, signaling our
|
||||
- // setters that _synchronize() needs to be called again.
|
||||
- void ApplicationState::_write() const noexcept
|
||||
- try
|
||||
- {
|
||||
- Json::Value root{ Json::objectValue };
|
||||
-
|
||||
- {
|
||||
- auto state = _state.lock_shared();
|
||||
-#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) JsonUtils::SetValueForKey(root, key, state->name);
|
||||
- MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
-#undef MTSM_APPLICATION_STATE_GEN
|
||||
- }
|
||||
-
|
||||
- Json::StreamWriterBuilder wbuilder;
|
||||
- const auto content = Json::writeString(wbuilder, root);
|
||||
- WriteUTF8FileAtomic(_path, content);
|
||||
- }
|
||||
- CATCH_LOG()
|
||||
}
|
||||
diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.h b/src/cascadia/TerminalSettingsModel/ApplicationState.h
|
||||
index 71c6a576e..f7011289e 100644
|
||||
--- a/src/cascadia/TerminalSettingsModel/ApplicationState.h
|
||||
+++ b/src/cascadia/TerminalSettingsModel/ApplicationState.h
|
||||
@@ -12,12 +12,11 @@ Abstract:
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
+#include "BaseApplicationState.h"
|
||||
#include "ApplicationState.g.h"
|
||||
#include "WindowLayout.g.h"
|
||||
|
||||
#include <inc/cppwinrt_utils.h>
|
||||
-#include <til/mutex.h>
|
||||
-#include <til/throttled_func.h>
|
||||
#include <JsonUtils.h>
|
||||
|
||||
// This macro generates all getters and setters for ApplicationState.
|
||||
@@ -40,18 +39,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
friend ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait<Model::WindowLayout>;
|
||||
};
|
||||
|
||||
- struct ApplicationState : ApplicationStateT<ApplicationState>
|
||||
+ struct ApplicationState : public BaseApplicationState, ApplicationStateT<ApplicationState>
|
||||
{
|
||||
static Microsoft::Terminal::Settings::Model::ApplicationState SharedInstance();
|
||||
|
||||
ApplicationState(std::filesystem::path path) noexcept;
|
||||
- ~ApplicationState();
|
||||
|
||||
- // Methods
|
||||
- void Reload() const noexcept;
|
||||
-
|
||||
- // General getters/setters
|
||||
- winrt::hstring FilePath() const noexcept;
|
||||
+ virtual void FromJson(const Json::Value& root) const noexcept override;
|
||||
+ virtual Json::Value ToJson() const noexcept override;
|
||||
|
||||
// State getters/setters
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \
|
||||
@@ -67,13 +62,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
};
|
||||
-
|
||||
- void _write() const noexcept;
|
||||
- void _read() const noexcept;
|
||||
-
|
||||
- std::filesystem::path _path;
|
||||
til::shared_mutex<state_t> _state;
|
||||
- til::throttled_func_trailing<> _throttler;
|
||||
};
|
||||
}
|
||||
|
||||
diff --git a/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp b/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp
|
||||
new file mode 100644
|
||||
index 000000000..3b5c02446
|
||||
--- /dev/null
|
||||
+++ b/src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp
|
||||
@@ -0,0 +1,92 @@
|
||||
+// Copyright (c) Microsoft Corporation.
|
||||
+// Licensed under the MIT license.
|
||||
+
|
||||
+#include "pch.h"
|
||||
+#include "BaseApplicationState.h"
|
||||
+#include "CascadiaSettings.h"
|
||||
+
|
||||
+#include "JsonUtils.h"
|
||||
+#include "FileUtils.h"
|
||||
+
|
||||
+constexpr std::wstring_view stateFileName{ L"state.json" };
|
||||
+using namespace ::Microsoft::Terminal::Settings::Model;
|
||||
+
|
||||
+BaseApplicationState::BaseApplicationState(std::filesystem::path path) noexcept :
|
||||
+ _path{ std::move(path) },
|
||||
+ _throttler{ std::chrono::seconds(1), [this]() { _write(); } }
|
||||
+{
|
||||
+ // DON'T _read() here! _read() will call FromJson, which is virtual, and
|
||||
+ // needs to be implemented in a derived class. Classes that derive from
|
||||
+ // BaseApplicationState should make sure to call Reload() after construction
|
||||
+ // to ensure the data is loaded.
|
||||
+}
|
||||
+
|
||||
+// The destructor ensures that the last write is flushed to disk before returning.
|
||||
+BaseApplicationState::~BaseApplicationState()
|
||||
+{
|
||||
+ // This will ensure that we not just cancel the last outstanding timer,
|
||||
+ // but instead force it to run as soon as possible and wait for it to complete.
|
||||
+ _throttler.flush();
|
||||
+}
|
||||
+
|
||||
+// Re-read the state.json from disk.
|
||||
+void BaseApplicationState::Reload() const noexcept
|
||||
+{
|
||||
+ _read();
|
||||
+}
|
||||
+
|
||||
+// Returns the state.json path on the disk.
|
||||
+winrt::hstring BaseApplicationState::FilePath() const noexcept
|
||||
+{
|
||||
+ return winrt::hstring{ _path.wstring() };
|
||||
+}
|
||||
+
|
||||
+// Deserializes the state.json at _path into this ApplicationState.
|
||||
+// * ANY errors during app state will result in the creation of a new empty state.
|
||||
+// * ANY errors during runtime will result in changes being partially ignored.
|
||||
+void BaseApplicationState::_read() const noexcept
|
||||
+try
|
||||
+{
|
||||
+ const auto data = _readFileContents().value_or(std::string{});
|
||||
+ if (data.empty())
|
||||
+ {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ std::string errs;
|
||||
+ std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
|
||||
+
|
||||
+ Json::Value root;
|
||||
+ if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs))
|
||||
+ {
|
||||
+ throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
|
||||
+ }
|
||||
+
|
||||
+ FromJson(root);
|
||||
+}
|
||||
+CATCH_LOG()
|
||||
+
|
||||
+// Serialized this ApplicationState (in `context`) into the state.json at _path.
|
||||
+// * Errors are only logged.
|
||||
+// * _state->_writeScheduled is set to false, signaling our
|
||||
+// setters that _synchronize() needs to be called again.
|
||||
+void BaseApplicationState::_write() const noexcept
|
||||
+try
|
||||
+{
|
||||
+ Json::Value root{ this->ToJson() };
|
||||
+
|
||||
+ Json::StreamWriterBuilder wbuilder;
|
||||
+ const auto content = Json::writeString(wbuilder, root);
|
||||
+ _writeFileContents(content);
|
||||
+}
|
||||
+CATCH_LOG()
|
||||
+
|
||||
+std::optional<std::string> BaseApplicationState::_readFileContents() const
|
||||
+{
|
||||
+ return ReadUTF8FileIfExists(_path);
|
||||
+}
|
||||
+
|
||||
+void BaseApplicationState::_writeFileContents(const std::string_view content) const
|
||||
+{
|
||||
+ WriteUTF8FileAtomic(_path, content);
|
||||
+}
|
||||
diff --git a/src/cascadia/TerminalSettingsModel/BaseApplicationState.h b/src/cascadia/TerminalSettingsModel/BaseApplicationState.h
|
||||
new file mode 100644
|
||||
index 000000000..e684d3192
|
||||
--- /dev/null
|
||||
+++ b/src/cascadia/TerminalSettingsModel/BaseApplicationState.h
|
||||
@@ -0,0 +1,36 @@
|
||||
+/*++
|
||||
+Copyright (c) Microsoft Corporation
|
||||
+Licensed under the MIT license.
|
||||
+
|
||||
+Module Name:
|
||||
+- ApplicationState.h
|
||||
+
|
||||
+Abstract:
|
||||
+- TODO!
|
||||
+--*/
|
||||
+#pragma once
|
||||
+
|
||||
+struct BaseApplicationState
|
||||
+{
|
||||
+ BaseApplicationState(std::filesystem::path path) noexcept;
|
||||
+ ~BaseApplicationState();
|
||||
+
|
||||
+ // Methods
|
||||
+ void Reload() const noexcept;
|
||||
+
|
||||
+ // General getters/setters
|
||||
+ winrt::hstring FilePath() const noexcept;
|
||||
+
|
||||
+ virtual void FromJson(const Json::Value& root) const noexcept = 0;
|
||||
+ virtual Json::Value ToJson() const noexcept = 0;
|
||||
+
|
||||
+protected:
|
||||
+ virtual std::optional<std::string> _readFileContents() const;
|
||||
+ virtual void _writeFileContents(const std::string_view content) const;
|
||||
+
|
||||
+ void _write() const noexcept;
|
||||
+ void _read() const noexcept;
|
||||
+
|
||||
+ std::filesystem::path _path;
|
||||
+ til::throttled_func_trailing<> _throttler;
|
||||
+};
|
||||
diff --git a/src/cascadia/TerminalSettingsModel/ElevatedState.cpp b/src/cascadia/TerminalSettingsModel/ElevatedState.cpp
|
||||
new file mode 100644
|
||||
index 000000000..f7d72207e
|
||||
--- /dev/null
|
||||
+++ b/src/cascadia/TerminalSettingsModel/ElevatedState.cpp
|
||||
@@ -0,0 +1,134 @@
|
||||
+// Copyright (c) Microsoft Corporation.
|
||||
+// Licensed under the MIT license.
|
||||
+
|
||||
+#include "pch.h"
|
||||
+#include "ElevatedState.h"
|
||||
+#include "CascadiaSettings.h"
|
||||
+#include "ElevatedState.g.cpp"
|
||||
+
|
||||
+#include "JsonUtils.h"
|
||||
+#include "FileUtils.h"
|
||||
+
|
||||
+#include <aclapi.h>
|
||||
+
|
||||
+constexpr std::wstring_view stateFileName{ L"elevated-state.json" };
|
||||
+
|
||||
+using namespace ::Microsoft::Terminal::Settings::Model;
|
||||
+
|
||||
+namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
+{
|
||||
+ ElevatedState::ElevatedState(std::filesystem::path path) noexcept :
|
||||
+ BaseApplicationState{ path } {}
|
||||
+
|
||||
+ // Returns the application-global ElevatedState object.
|
||||
+ Microsoft::Terminal::Settings::Model::ElevatedState ElevatedState::SharedInstance()
|
||||
+ {
|
||||
+ // TODO! place in a totally different file! and path!
|
||||
+ static auto state = winrt::make_self<ElevatedState>(GetBaseSettingsPath() / stateFileName);
|
||||
+ state->Reload();
|
||||
+
|
||||
+ // const auto testPath{ GetBaseSettingsPath() / L"test.json" };
|
||||
+
|
||||
+ // PSID pEveryoneSID = NULL;
|
||||
+ // SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_NT_AUTHORITY;
|
||||
+ // BOOL success = AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &pEveryoneSID);
|
||||
+
|
||||
+ // EXPLICIT_ACCESS ea[1];
|
||||
+ // ZeroMemory(&ea, 1 * sizeof(EXPLICIT_ACCESS));
|
||||
+ // ea[0].grfAccessPermissions = KEY_READ;
|
||||
+ // ea[0].grfAccessMode = SET_ACCESS;
|
||||
+ // ea[0].grfInheritance = NO_INHERITANCE;
|
||||
+ // ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
+ // ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
|
||||
+ // ea[0].Trustee.ptstrName = (LPTSTR)pEveryoneSID;
|
||||
+
|
||||
+ // ACL acl;
|
||||
+ // PACL pAcl = &acl;
|
||||
+ // DWORD dwRes = SetEntriesInAcl(1, ea, NULL, &pAcl);
|
||||
+ // dwRes;
|
||||
+
|
||||
+ // SECURITY_DESCRIPTOR sd;
|
||||
+ // success = InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
|
||||
+ // success = SetSecurityDescriptorDacl(&sd,
|
||||
+ // TRUE, // bDaclPresent flag
|
||||
+ // pAcl,
|
||||
+ // FALSE);
|
||||
+
|
||||
+ // SECURITY_ATTRIBUTES sa;
|
||||
+ // // Initialize a security attributes structure.
|
||||
+ // sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
+ // sa.lpSecurityDescriptor = &sd;
|
||||
+ // sa.bInheritHandle = FALSE;
|
||||
+ // success;
|
||||
+
|
||||
+ // wil::unique_hfile file{ CreateFileW(testPath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) };
|
||||
+ // THROW_LAST_ERROR_IF(!file);
|
||||
+
|
||||
+ return *state;
|
||||
+ }
|
||||
+
|
||||
+ void ElevatedState::FromJson(const Json::Value& root) const noexcept
|
||||
+ {
|
||||
+ auto state = _state.lock();
|
||||
+ // GetValueForKey() comes in two variants:
|
||||
+ // * take a std::optional<T> reference
|
||||
+ // * return std::optional<T> by value
|
||||
+ // At the time of writing the former version skips missing fields in the json,
|
||||
+ // but we want to explicitly clear state fields that were removed from state.json.
|
||||
+#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) state->name = JsonUtils::GetValueForKey<std::optional<type>>(root, key);
|
||||
+ MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN)
|
||||
+#undef MTSM_ELEVATED_STATE_GEN
|
||||
+ }
|
||||
+ Json::Value ElevatedState::ToJson() const noexcept
|
||||
+ {
|
||||
+ Json::Value root{ Json::objectValue };
|
||||
+
|
||||
+ {
|
||||
+ auto state = _state.lock_shared();
|
||||
+#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) JsonUtils::SetValueForKey(root, key, state->name);
|
||||
+ MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN)
|
||||
+#undef MTSM_ELEVATED_STATE_GEN
|
||||
+ }
|
||||
+ return root;
|
||||
+ }
|
||||
+
|
||||
+ // Generate all getter/setters
|
||||
+#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) \
|
||||
+ type ElevatedState::name() const noexcept \
|
||||
+ { \
|
||||
+ const auto state = _state.lock_shared(); \
|
||||
+ const auto& value = state->name; \
|
||||
+ return value ? *value : type{ __VA_ARGS__ }; \
|
||||
+ } \
|
||||
+ \
|
||||
+ void ElevatedState::name(const type& value) noexcept \
|
||||
+ { \
|
||||
+ { \
|
||||
+ auto state = _state.lock(); \
|
||||
+ state->name.emplace(value); \
|
||||
+ } \
|
||||
+ \
|
||||
+ _throttler(); \
|
||||
+ }
|
||||
+ MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN)
|
||||
+#undef MTSM_ELEVATED_STATE_GEN
|
||||
+
|
||||
+ void ElevatedState::_writeFileContents(const std::string_view content) const
|
||||
+ {
|
||||
+ // DON'T use WriteUTF8FileAtomic, which will write to a temporary file
|
||||
+ // then rename that file to the final filename. That actually lets us
|
||||
+ // overwrite the elevate file's contents even when unelevated, because
|
||||
+ // we're effectively deleting the original file, then renaming a
|
||||
+ // different file in it's place.
|
||||
+ //
|
||||
+ // We're not worried about someone else doing that though, if they do
|
||||
+ // that with the wrong permissions, then we'll just ignore the file and
|
||||
+ // start over.
|
||||
+ WriteUTF8File(_path, content, true);
|
||||
+ }
|
||||
+
|
||||
+ std::optional<std::string> ElevatedState::_readFileContents() const
|
||||
+ {
|
||||
+ return ReadUTF8FileIfExists(_path, true);
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/cascadia/TerminalSettingsModel/ElevatedState.h b/src/cascadia/TerminalSettingsModel/ElevatedState.h
|
||||
new file mode 100644
|
||||
index 000000000..d0eb8381f
|
||||
--- /dev/null
|
||||
+++ b/src/cascadia/TerminalSettingsModel/ElevatedState.h
|
||||
@@ -0,0 +1,60 @@
|
||||
+/*++
|
||||
+Copyright (c) Microsoft Corporation
|
||||
+Licensed under the MIT license.
|
||||
+
|
||||
+Module Name:
|
||||
+- ElevatedState.h
|
||||
+
|
||||
+Abstract:
|
||||
+- If the CascadiaSettings class were AppData, then this class would be LocalAppData.
|
||||
+ Put anything in here that you wouldn't want to be stored next to user-editable settings.
|
||||
+- Modify ElevatedState.idl and MTSM_ELEVATED_STATE_FIELDS to add new fields.
|
||||
+--*/
|
||||
+#pragma once
|
||||
+
|
||||
+#include "BaseApplicationState.h"
|
||||
+#include "ElevatedState.g.h"
|
||||
+#include <inc/cppwinrt_utils.h>
|
||||
+
|
||||
+// This macro generates all getters and setters for ElevatedState.
|
||||
+// It provides X with the following arguments:
|
||||
+// (type, function name, JSON key, ...variadic construction arguments)
|
||||
+#define MTSM_ELEVATED_STATE_FIELDS(X) \
|
||||
+ X(Windows::Foundation::Collections::IVector<hstring>, AllowedCommandlines, "allowedCommandlines")
|
||||
+
|
||||
+namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
+{
|
||||
+ struct ElevatedState : ElevatedStateT<ElevatedState>, public BaseApplicationState
|
||||
+ {
|
||||
+ static Microsoft::Terminal::Settings::Model::ElevatedState SharedInstance();
|
||||
+
|
||||
+ ElevatedState(std::filesystem::path path) noexcept;
|
||||
+
|
||||
+ void FromJson(const Json::Value& root) const noexcept override;
|
||||
+ Json::Value ToJson() const noexcept override;
|
||||
+
|
||||
+ // State getters/setters
|
||||
+#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) \
|
||||
+ type name() const noexcept; \
|
||||
+ void name(const type& value) noexcept;
|
||||
+ MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN)
|
||||
+#undef MTSM_ELEVATED_STATE_GEN
|
||||
+
|
||||
+ private:
|
||||
+ struct state_t
|
||||
+ {
|
||||
+#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) std::optional<type> name{ __VA_ARGS__ };
|
||||
+ MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN)
|
||||
+#undef MTSM_ELEVATED_STATE_GEN
|
||||
+ };
|
||||
+ til::shared_mutex<state_t> _state;
|
||||
+
|
||||
+ virtual std::optional<std::string> _readFileContents() const override;
|
||||
+ virtual void _writeFileContents(const std::string_view content) const override;
|
||||
+ };
|
||||
+}
|
||||
+
|
||||
+namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
|
||||
+{
|
||||
+ BASIC_FACTORY(ElevatedState);
|
||||
+}
|
||||
diff --git a/src/cascadia/TerminalSettingsModel/ElevatedState.idl b/src/cascadia/TerminalSettingsModel/ElevatedState.idl
|
||||
new file mode 100644
|
||||
index 000000000..09e2d05b0
|
||||
--- /dev/null
|
||||
+++ b/src/cascadia/TerminalSettingsModel/ElevatedState.idl
|
||||
@@ -0,0 +1,15 @@
|
||||
+// Copyright (c) Microsoft Corporation.
|
||||
+// Licensed under the MIT license.
|
||||
+
|
||||
+namespace Microsoft.Terminal.Settings.Model
|
||||
+{
|
||||
+ [default_interface] runtimeclass ElevatedState {
|
||||
+ static ElevatedState SharedInstance();
|
||||
+
|
||||
+ void Reload();
|
||||
+
|
||||
+ String FilePath { get; };
|
||||
+
|
||||
+ Windows.Foundation.Collections.IVector<String> AllowedCommandlines { get; set; };
|
||||
+ }
|
||||
+}
|
||||
diff --git a/src/cascadia/TerminalSettingsModel/FileUtils.cpp b/src/cascadia/TerminalSettingsModel/FileUtils.cpp
|
||||
index 81fbb6cee..dff35897a 100644
|
||||
--- a/src/cascadia/TerminalSettingsModel/FileUtils.cpp
|
||||
+++ b/src/cascadia/TerminalSettingsModel/FileUtils.cpp
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <shlobj.h>
|
||||
#include <WtExeUtils.h>
|
||||
|
||||
+#include <aclapi.h>
|
||||
+
|
||||
static constexpr std::string_view Utf8Bom{ u8"\uFEFF" };
|
||||
static constexpr std::wstring_view UnpackagedSettingsFolderName{ L"Microsoft\\Windows Terminal\\" };
|
||||
|
||||
@@ -39,10 +41,89 @@ namespace Microsoft::Terminal::Settings::Model
|
||||
return baseSettingsPath;
|
||||
}
|
||||
|
||||
+ static bool _hasExpectedPermissions(const std::filesystem::path& path)
|
||||
+ {
|
||||
+ // If we want to only open the file if it's elevated, check the
|
||||
+ // permissions on this file. We want to make sure that:
|
||||
+ // * Everyone has permission to read
|
||||
+ // * admins can do anything
|
||||
+ // * no one else can do anything.
|
||||
+ PACL pAcl{ nullptr }; // This doesn't need to be cleanup up apparently
|
||||
+
|
||||
+ auto status = GetNamedSecurityInfo(path.c_str(),
|
||||
+ SE_FILE_OBJECT,
|
||||
+ DACL_SECURITY_INFORMATION,
|
||||
+ nullptr,
|
||||
+ nullptr,
|
||||
+ &pAcl,
|
||||
+ nullptr,
|
||||
+ nullptr);
|
||||
+ THROW_IF_WIN32_ERROR(status);
|
||||
+
|
||||
+ PEXPLICIT_ACCESS pEA{ nullptr };
|
||||
+ DWORD count = 0;
|
||||
+ status = GetExplicitEntriesFromAcl(pAcl, &count, &pEA);
|
||||
+ THROW_IF_WIN32_ERROR(status);
|
||||
+
|
||||
+ auto explicitAccessCleanup = wil::scope_exit([&]() { ::LocalFree(pEA); });
|
||||
+
|
||||
+ if (count != 2)
|
||||
+ {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ // Now, get the Everyone and Admins SIDS so we can make sure they're
|
||||
+ // the ones in this file.
|
||||
+
|
||||
+ wil::unique_sid everyoneSid{};
|
||||
+ wil::unique_sid adminGroupSid{};
|
||||
+ SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;
|
||||
+ SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
|
||||
+
|
||||
+ // Create a SID for the BUILTIN\Administrators group.
|
||||
+ THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminGroupSid));
|
||||
+
|
||||
+ // Create a well-known SID for the Everyone group.
|
||||
+ THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &everyoneSid));
|
||||
+
|
||||
+ bool hadExpectedPermissions = true;
|
||||
+
|
||||
+ // Check that the permissions are what we'd expect them to be if only
|
||||
+ // admins can write to the file. This is basically a mirror of what we
|
||||
+ // set up in `WriteUTF8File`.
|
||||
+
|
||||
+ // For grfAccessPermissions, GENERIC_ALL turns into STANDARD_RIGHTS_ALL,
|
||||
+ // and GENERIC_READ -> READ_CONTROL
|
||||
+ hadExpectedPermissions = hadExpectedPermissions && WI_AreAllFlagsSet(pEA[0].grfAccessPermissions, STANDARD_RIGHTS_ALL);
|
||||
+ hadExpectedPermissions = hadExpectedPermissions && pEA[0].grfInheritance == NO_INHERITANCE;
|
||||
+ hadExpectedPermissions = hadExpectedPermissions && pEA[0].Trustee.TrusteeForm == TRUSTEE_IS_SID;
|
||||
+ // SIDs are void*'s that happen to convert to a wchar_t
|
||||
+ hadExpectedPermissions = hadExpectedPermissions && *(pEA[0].Trustee.ptstrName) == *(LPWSTR)(adminGroupSid.get());
|
||||
+
|
||||
+ // Now check the other EXPLICIT_ACCESS
|
||||
+ hadExpectedPermissions = hadExpectedPermissions && WI_IsFlagSet(pEA[1].grfAccessPermissions, READ_CONTROL);
|
||||
+ hadExpectedPermissions = hadExpectedPermissions && pEA[1].grfInheritance == NO_INHERITANCE;
|
||||
+ hadExpectedPermissions = hadExpectedPermissions && pEA[1].Trustee.TrusteeForm == TRUSTEE_IS_SID;
|
||||
+ hadExpectedPermissions = hadExpectedPermissions && *(pEA[1].Trustee.ptstrName) == *(LPWSTR)(everyoneSid.get());
|
||||
+
|
||||
+ return hadExpectedPermissions;
|
||||
+ }
|
||||
// Tries to read a file somewhat atomically without locking it.
|
||||
// Strips the UTF8 BOM if it exists.
|
||||
- std::string ReadUTF8File(const std::filesystem::path& path)
|
||||
+ std::string ReadUTF8File(const std::filesystem::path& path, const bool elevatedOnly)
|
||||
{
|
||||
+ if (elevatedOnly)
|
||||
+ {
|
||||
+ const bool hadExpectedPermissions{ _hasExpectedPermissions(path) };
|
||||
+ if (!hadExpectedPermissions)
|
||||
+ {
|
||||
+ // delete the file. It's been compromised.
|
||||
+ LOG_LAST_ERROR_IF(!DeleteFile(path.c_str()));
|
||||
+ // Exit early, because obviously there's nothing to read from the deleted file.
|
||||
+ return "";
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
// From some casual observations we can determine that:
|
||||
// * ReadFile() always returns the requested amount of data (unless the file is smaller)
|
||||
// * It's unlikely that the file was changed between GetFileSize() and ReadFile()
|
||||
@@ -89,11 +170,11 @@ namespace Microsoft::Terminal::Settings::Model
|
||||
}
|
||||
|
||||
// Same as ReadUTF8File, but returns an empty optional, if the file couldn't be opened.
|
||||
- std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path)
|
||||
+ std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path, const bool elevatedOnly)
|
||||
{
|
||||
try
|
||||
{
|
||||
- return { ReadUTF8File(path) };
|
||||
+ return { ReadUTF8File(path, elevatedOnly) };
|
||||
}
|
||||
catch (const wil::ResultException& exception)
|
||||
{
|
||||
@@ -106,9 +187,75 @@ namespace Microsoft::Terminal::Settings::Model
|
||||
}
|
||||
}
|
||||
|
||||
- void WriteUTF8File(const std::filesystem::path& path, const std::string_view content)
|
||||
+ void WriteUTF8File(const std::filesystem::path& path,
|
||||
+ const std::string_view content,
|
||||
+ const bool elevatedOnly)
|
||||
{
|
||||
- wil::unique_hfile file{ CreateFileW(path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) };
|
||||
+ SECURITY_ATTRIBUTES sa;
|
||||
+ if (elevatedOnly)
|
||||
+ {
|
||||
+ // This is very vaguely taken from
|
||||
+ // https://docs.microsoft.com/en-us/windows/win32/secauthz/creating-a-security-descriptor-for-a-new-object-in-c--
|
||||
+ // With using https://docs.microsoft.com/en-us/windows/win32/secauthz/well-known-sids
|
||||
+ // to find out that
|
||||
+ // * SECURITY_NT_AUTHORITY+SECURITY_LOCAL_SYSTEM_RID == NT AUTHORITY\SYSTEM
|
||||
+ // * SECURITY_NT_AUTHORITY+SECURITY_BUILTIN_DOMAIN_RID+DOMAIN_ALIAS_RID_ADMINS == BUILTIN\Administrators
|
||||
+ // * SECURITY_WORLD_SID_AUTHORITY+SECURITY_WORLD_RID == Everyone
|
||||
+ //
|
||||
+ // Raymond Chen recommended that I make this file only writable by
|
||||
+ // SYSTEM, but if I did that, then even we can't write the file
|
||||
+ // while elevated, which isn't what we want.
|
||||
+
|
||||
+ wil::unique_sid everyoneSid{};
|
||||
+ wil::unique_sid adminGroupSid{};
|
||||
+ SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;
|
||||
+ SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
|
||||
+
|
||||
+ // Create a SID for the BUILTIN\Administrators group.
|
||||
+ THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminGroupSid));
|
||||
+
|
||||
+ // Create a well-known SID for the Everyone group.
|
||||
+ THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &everyoneSid));
|
||||
+
|
||||
+ EXPLICIT_ACCESS ea[2];
|
||||
+ ZeroMemory(&ea, 2 * sizeof(EXPLICIT_ACCESS));
|
||||
+ // Grant Admins all permissions on this file
|
||||
+ ea[0].grfAccessPermissions = GENERIC_ALL;
|
||||
+ ea[0].grfAccessMode = SET_ACCESS;
|
||||
+ ea[0].grfInheritance = NO_INHERITANCE;
|
||||
+ ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
+ ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
|
||||
+ ea[0].Trustee.ptstrName = (LPWSTR)(adminGroupSid.get());
|
||||
+
|
||||
+ // Grant Everyone the permission or read this file
|
||||
+ ea[1].grfAccessPermissions = GENERIC_READ;
|
||||
+ ea[1].grfAccessMode = SET_ACCESS;
|
||||
+ ea[1].grfInheritance = NO_INHERITANCE;
|
||||
+ ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
+ ea[1].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
|
||||
+ ea[1].Trustee.ptstrName = (LPWSTR)(everyoneSid.get());
|
||||
+
|
||||
+ ACL acl;
|
||||
+ PACL pAcl = &acl;
|
||||
+ THROW_IF_WIN32_ERROR(SetEntriesInAcl(2, ea, nullptr, &pAcl));
|
||||
+
|
||||
+ SECURITY_DESCRIPTOR sd;
|
||||
+ THROW_IF_WIN32_BOOL_FALSE(InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION));
|
||||
+ THROW_IF_WIN32_BOOL_FALSE(SetSecurityDescriptorDacl(&sd, true, pAcl, false));
|
||||
+
|
||||
+ // Initialize a security attributes structure.
|
||||
+ sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
+ sa.lpSecurityDescriptor = &sd;
|
||||
+ sa.bInheritHandle = false;
|
||||
+ }
|
||||
+
|
||||
+ wil::unique_hfile file{ CreateFileW(path.c_str(),
|
||||
+ GENERIC_WRITE,
|
||||
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
+ elevatedOnly ? &sa : nullptr,
|
||||
+ CREATE_ALWAYS,
|
||||
+ FILE_ATTRIBUTE_NORMAL,
|
||||
+ nullptr) };
|
||||
THROW_LAST_ERROR_IF(!file);
|
||||
|
||||
const auto fileSize = gsl::narrow<DWORD>(content.size());
|
||||
@@ -121,7 +268,8 @@ namespace Microsoft::Terminal::Settings::Model
|
||||
}
|
||||
}
|
||||
|
||||
- void WriteUTF8FileAtomic(const std::filesystem::path& path, const std::string_view content)
|
||||
+ void WriteUTF8FileAtomic(const std::filesystem::path& path,
|
||||
+ const std::string_view content)
|
||||
{
|
||||
// GH#10787: rename() will replace symbolic links themselves and not the path they point at.
|
||||
// It's thus important that we first resolve them before generating temporary path.
|
||||
diff --git a/src/cascadia/TerminalSettingsModel/FileUtils.h b/src/cascadia/TerminalSettingsModel/FileUtils.h
|
||||
index d2e2eb53c..0426b4dd5 100644
|
||||
--- a/src/cascadia/TerminalSettingsModel/FileUtils.h
|
||||
+++ b/src/cascadia/TerminalSettingsModel/FileUtils.h
|
||||
@@ -4,8 +4,8 @@
|
||||
namespace Microsoft::Terminal::Settings::Model
|
||||
{
|
||||
std::filesystem::path GetBaseSettingsPath();
|
||||
- std::string ReadUTF8File(const std::filesystem::path& path);
|
||||
- std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path);
|
||||
- void WriteUTF8File(const std::filesystem::path& path, const std::string_view content);
|
||||
+ std::string ReadUTF8File(const std::filesystem::path& path, const bool elevatedOnly = false);
|
||||
+ std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path, const bool elevatedOnly = false);
|
||||
+ void WriteUTF8File(const std::filesystem::path& path, const std::string_view content, const bool elevatedOnly = false);
|
||||
void WriteUTF8FileAtomic(const std::filesystem::path& path, const std::string_view content);
|
||||
}
|
||||
diff --git a/src/cascadia/TerminalSettingsModel/JsonUtils.h b/src/cascadia/TerminalSettingsModel/JsonUtils.h
|
||||
index 58124d84b..1b26f7bae 100644
|
||||
--- a/src/cascadia/TerminalSettingsModel/JsonUtils.h
|
||||
+++ b/src/cascadia/TerminalSettingsModel/JsonUtils.h
|
||||
@@ -382,7 +382,6 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
-
|
||||
struct ConversionTrait<std::unordered_map<std::string, T>>
|
||||
{
|
||||
std::unordered_map<std::string, T> FromJson(const Json::Value& json) const
|
||||
diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj
|
||||
index 202f07808..ce12741d1 100644
|
||||
--- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj
|
||||
+++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj
|
||||
@@ -21,6 +21,7 @@
|
||||
<ClInclude Include="IconPathConverter.h">
|
||||
<DependentUpon>IconPathConverter.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
+ <ClInclude Include="BaseApplicationState.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="ActionArgs.h">
|
||||
<DependentUpon>ActionArgs.idl</DependentUpon>
|
||||
@@ -35,6 +36,9 @@
|
||||
<ClInclude Include="ApplicationState.h">
|
||||
<DependentUpon>ApplicationState.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
+ <ClInclude Include="ElevatedState.h">
|
||||
+ <DependentUpon>ElevatedState.idl</DependentUpon>
|
||||
+ </ClInclude>
|
||||
<ClInclude Include="CascadiaSettings.h">
|
||||
<DependentUpon>CascadiaSettings.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
@@ -88,6 +92,7 @@
|
||||
<DependentUpon>IconPathConverter.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="init.cpp" />
|
||||
+ <ClCompile Include="BaseApplicationState.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
@@ -107,6 +112,9 @@
|
||||
<ClCompile Include="ApplicationState.cpp">
|
||||
<DependentUpon>ApplicationState.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
+ <ClCompile Include="ElevatedState.cpp">
|
||||
+ <DependentUpon>ElevatedState.idl</DependentUpon>
|
||||
+ </ClCompile>
|
||||
<ClCompile Include="CascadiaSettings.cpp">
|
||||
<DependentUpon>CascadiaSettings.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
@@ -156,6 +164,7 @@
|
||||
<Midl Include="ActionArgs.idl" />
|
||||
<Midl Include="ActionMap.idl" />
|
||||
<Midl Include="ApplicationState.idl" />
|
||||
+ <Midl Include="ElevatedState.idl" />
|
||||
<Midl Include="CascadiaSettings.idl" />
|
||||
<Midl Include="ColorScheme.idl" />
|
||||
<Midl Include="Command.idl" />
|
||||
diff --git a/src/cascadia/TerminalSettingsModel/pch.h b/src/cascadia/TerminalSettingsModel/pch.h
|
||||
index d5473939c..25773a45d 100644
|
||||
--- a/src/cascadia/TerminalSettingsModel/pch.h
|
||||
+++ b/src/cascadia/TerminalSettingsModel/pch.h
|
||||
@@ -53,3 +53,6 @@ TRACELOGGING_DECLARE_PROVIDER(g_hSettingsModelProvider);
|
||||
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#include "til.h"
|
||||
+
|
||||
+#include <til/mutex.h>
|
||||
+#include <til/throttled_func.h>
|
|
@ -61,6 +61,9 @@ Author(s):
|
|||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#include "til.h"
|
||||
|
||||
#include <til/mutex.h>
|
||||
#include <til/throttled_func.h>
|
||||
|
||||
// Common includes for most tests:
|
||||
#include "../../inc/argb.h"
|
||||
#include "../../inc/conattrs.hpp"
|
||||
|
|
|
@ -210,7 +210,7 @@ namespace winrt::TerminalApp::implementation
|
|||
}
|
||||
|
||||
AppLogic::AppLogic() :
|
||||
_reloadState{ std::chrono::milliseconds(100), []() { ApplicationState::SharedInstance().Reload(); } }
|
||||
_reloadState{ std::chrono::milliseconds(100), []() { ApplicationState::SharedInstance().Reload(); ElevatedState::SharedInstance().Reload(); } }
|
||||
{
|
||||
// For your own sanity, it's better to do setup outside the ctor.
|
||||
// If you do any setup in the ctor that ends up throwing an exception,
|
||||
|
@ -927,8 +927,6 @@ namespace winrt::TerminalApp::implementation
|
|||
void AppLogic::_RegisterSettingsChange()
|
||||
{
|
||||
const std::filesystem::path settingsPath{ std::wstring_view{ CascadiaSettings::SettingsPath() } };
|
||||
const std::filesystem::path statePath{ std::wstring_view{ ApplicationState::SharedInstance().FilePath() } };
|
||||
|
||||
_reader.create(
|
||||
settingsPath.parent_path().c_str(),
|
||||
false,
|
||||
|
@ -937,14 +935,17 @@ namespace winrt::TerminalApp::implementation
|
|||
// editors, who will write a temp file, then rename it to be the
|
||||
// actual file you wrote. So listen for that too.
|
||||
wil::FolderChangeEvents::FileName | wil::FolderChangeEvents::LastWriteTime,
|
||||
[this, settingsBasename = settingsPath.filename(), stateBasename = statePath.filename()](wil::FolderChangeEvent, PCWSTR fileModified) {
|
||||
[this, settingsPath](wil::FolderChangeEvent, PCWSTR fileModified) {
|
||||
static const std::filesystem::path statePath{ std::wstring_view{ ApplicationState::SharedInstance().FilePath() } };
|
||||
static const std::filesystem::path elevatedStatePath{ std::wstring_view{ ElevatedState::SharedInstance().FilePath() } };
|
||||
|
||||
const auto modifiedBasename = std::filesystem::path{ fileModified }.filename();
|
||||
|
||||
if (modifiedBasename == settingsBasename)
|
||||
if (modifiedBasename == settingsPath.filename())
|
||||
{
|
||||
_reloadSettings->Run();
|
||||
}
|
||||
else if (modifiedBasename == stateBasename)
|
||||
else if (modifiedBasename == statePath.filename() || modifiedBasename == elevatedStatePath.filename())
|
||||
{
|
||||
_reloadState();
|
||||
}
|
||||
|
|
|
@ -60,38 +60,40 @@ using namespace ::Microsoft::Terminal::Settings::Model;
|
|||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
ApplicationState::ApplicationState(std::filesystem::path path) noexcept :
|
||||
BaseApplicationState{ path } {}
|
||||
|
||||
// Returns the application-global ApplicationState object.
|
||||
Microsoft::Terminal::Settings::Model::ApplicationState ApplicationState::SharedInstance()
|
||||
{
|
||||
static auto state = winrt::make_self<ApplicationState>(GetBaseSettingsPath() / stateFileName);
|
||||
state->Reload();
|
||||
return *state;
|
||||
}
|
||||
|
||||
ApplicationState::ApplicationState(std::filesystem::path path) noexcept :
|
||||
_path{ std::move(path) },
|
||||
_throttler{ std::chrono::seconds(1), [this]() { _write(); } }
|
||||
void ApplicationState::FromJson(const Json::Value& root) const noexcept
|
||||
{
|
||||
_read();
|
||||
auto state = _state.lock();
|
||||
// GetValueForKey() comes in two variants:
|
||||
// * take a std::optional<T> reference
|
||||
// * return std::optional<T> by value
|
||||
// At the time of writing the former version skips missing fields in the json,
|
||||
// but we want to explicitly clear state fields that were removed from state.json.
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) state->name = JsonUtils::GetValueForKey<std::optional<type>>(root, key);
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
}
|
||||
|
||||
// The destructor ensures that the last write is flushed to disk before returning.
|
||||
ApplicationState::~ApplicationState()
|
||||
Json::Value ApplicationState::ToJson() const noexcept
|
||||
{
|
||||
// This will ensure that we not just cancel the last outstanding timer,
|
||||
// but instead force it to run as soon as possible and wait for it to complete.
|
||||
_throttler.flush();
|
||||
}
|
||||
Json::Value root{ Json::objectValue };
|
||||
|
||||
// Re-read the state.json from disk.
|
||||
void ApplicationState::Reload() const noexcept
|
||||
{
|
||||
_read();
|
||||
}
|
||||
|
||||
// Returns the state.json path on the disk.
|
||||
winrt::hstring ApplicationState::FilePath() const noexcept
|
||||
{
|
||||
return winrt::hstring{ _path.wstring() };
|
||||
{
|
||||
auto state = _state.lock_shared();
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) JsonUtils::SetValueForKey(root, key, state->name);
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
// Generate all getter/setters
|
||||
|
@ -115,58 +117,4 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
|
||||
// Deserializes the state.json at _path into this ApplicationState.
|
||||
// * ANY errors during app state will result in the creation of a new empty state.
|
||||
// * ANY errors during runtime will result in changes being partially ignored.
|
||||
void ApplicationState::_read() const noexcept
|
||||
try
|
||||
{
|
||||
const auto data = ReadUTF8FileIfExists(_path).value_or(std::string{});
|
||||
if (data.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::string errs;
|
||||
std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
|
||||
|
||||
Json::Value root;
|
||||
if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs))
|
||||
{
|
||||
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
|
||||
}
|
||||
|
||||
auto state = _state.lock();
|
||||
// GetValueForKey() comes in two variants:
|
||||
// * take a std::optional<T> reference
|
||||
// * return std::optional<T> by value
|
||||
// At the time of writing the former version skips missing fields in the json,
|
||||
// but we want to explicitly clear state fields that were removed from state.json.
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) state->name = JsonUtils::GetValueForKey<std::optional<type>>(root, key);
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
}
|
||||
CATCH_LOG()
|
||||
|
||||
// Serialized this ApplicationState (in `context`) into the state.json at _path.
|
||||
// * Errors are only logged.
|
||||
// * _state->_writeScheduled is set to false, signaling our
|
||||
// setters that _synchronize() needs to be called again.
|
||||
void ApplicationState::_write() const noexcept
|
||||
try
|
||||
{
|
||||
Json::Value root{ Json::objectValue };
|
||||
|
||||
{
|
||||
auto state = _state.lock_shared();
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) JsonUtils::SetValueForKey(root, key, state->name);
|
||||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
}
|
||||
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
const auto content = Json::writeString(wbuilder, root);
|
||||
WriteUTF8FileAtomic(_path, content);
|
||||
}
|
||||
CATCH_LOG()
|
||||
}
|
||||
|
|
|
@ -12,12 +12,11 @@ Abstract:
|
|||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "BaseApplicationState.h"
|
||||
#include "ApplicationState.g.h"
|
||||
#include "WindowLayout.g.h"
|
||||
|
||||
#include <inc/cppwinrt_utils.h>
|
||||
#include <til/mutex.h>
|
||||
#include <til/throttled_func.h>
|
||||
#include <JsonUtils.h>
|
||||
|
||||
// This macro generates all getters and setters for ApplicationState.
|
||||
|
@ -40,18 +39,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
friend ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait<Model::WindowLayout>;
|
||||
};
|
||||
|
||||
struct ApplicationState : ApplicationStateT<ApplicationState>
|
||||
struct ApplicationState : public BaseApplicationState, ApplicationStateT<ApplicationState>
|
||||
{
|
||||
static Microsoft::Terminal::Settings::Model::ApplicationState SharedInstance();
|
||||
|
||||
ApplicationState(std::filesystem::path path) noexcept;
|
||||
~ApplicationState();
|
||||
|
||||
// Methods
|
||||
void Reload() const noexcept;
|
||||
|
||||
// General getters/setters
|
||||
winrt::hstring FilePath() const noexcept;
|
||||
virtual void FromJson(const Json::Value& root) const noexcept override;
|
||||
virtual Json::Value ToJson() const noexcept override;
|
||||
|
||||
// State getters/setters
|
||||
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \
|
||||
|
@ -67,13 +62,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
|
||||
#undef MTSM_APPLICATION_STATE_GEN
|
||||
};
|
||||
|
||||
void _write() const noexcept;
|
||||
void _read() const noexcept;
|
||||
|
||||
std::filesystem::path _path;
|
||||
til::shared_mutex<state_t> _state;
|
||||
til::throttled_func_trailing<> _throttler;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
92
src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp
Normal file
92
src/cascadia/TerminalSettingsModel/BaseApplicationState.cpp
Normal file
|
@ -0,0 +1,92 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "BaseApplicationState.h"
|
||||
#include "CascadiaSettings.h"
|
||||
|
||||
#include "JsonUtils.h"
|
||||
#include "FileUtils.h"
|
||||
|
||||
constexpr std::wstring_view stateFileName{ L"state.json" };
|
||||
using namespace ::Microsoft::Terminal::Settings::Model;
|
||||
|
||||
BaseApplicationState::BaseApplicationState(std::filesystem::path path) noexcept :
|
||||
_path{ std::move(path) },
|
||||
_throttler{ std::chrono::seconds(1), [this]() { _write(); } }
|
||||
{
|
||||
// DON'T _read() here! _read() will call FromJson, which is virtual, and
|
||||
// needs to be implemented in a derived class. Classes that derive from
|
||||
// BaseApplicationState should make sure to call Reload() after construction
|
||||
// to ensure the data is loaded.
|
||||
}
|
||||
|
||||
// The destructor ensures that the last write is flushed to disk before returning.
|
||||
BaseApplicationState::~BaseApplicationState()
|
||||
{
|
||||
// This will ensure that we not just cancel the last outstanding timer,
|
||||
// but instead force it to run as soon as possible and wait for it to complete.
|
||||
_throttler.flush();
|
||||
}
|
||||
|
||||
// Re-read the state.json from disk.
|
||||
void BaseApplicationState::Reload() const noexcept
|
||||
{
|
||||
_read();
|
||||
}
|
||||
|
||||
// Returns the state.json path on the disk.
|
||||
winrt::hstring BaseApplicationState::FilePath() const noexcept
|
||||
{
|
||||
return winrt::hstring{ _path.wstring() };
|
||||
}
|
||||
|
||||
// Deserializes the state.json at _path into this ApplicationState.
|
||||
// * ANY errors during app state will result in the creation of a new empty state.
|
||||
// * ANY errors during runtime will result in changes being partially ignored.
|
||||
void BaseApplicationState::_read() const noexcept
|
||||
try
|
||||
{
|
||||
const auto data = _readFileContents().value_or(std::string{});
|
||||
if (data.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::string errs;
|
||||
std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
|
||||
|
||||
Json::Value root;
|
||||
if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs))
|
||||
{
|
||||
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
|
||||
}
|
||||
|
||||
FromJson(root);
|
||||
}
|
||||
CATCH_LOG()
|
||||
|
||||
// Serialized this ApplicationState (in `context`) into the state.json at _path.
|
||||
// * Errors are only logged.
|
||||
// * _state->_writeScheduled is set to false, signaling our
|
||||
// setters that _synchronize() needs to be called again.
|
||||
void BaseApplicationState::_write() const noexcept
|
||||
try
|
||||
{
|
||||
Json::Value root{ this->ToJson() };
|
||||
|
||||
Json::StreamWriterBuilder wbuilder;
|
||||
const auto content = Json::writeString(wbuilder, root);
|
||||
_writeFileContents(content);
|
||||
}
|
||||
CATCH_LOG()
|
||||
|
||||
std::optional<std::string> BaseApplicationState::_readFileContents() const
|
||||
{
|
||||
return ReadUTF8FileIfExists(_path);
|
||||
}
|
||||
|
||||
void BaseApplicationState::_writeFileContents(const std::string_view content) const
|
||||
{
|
||||
WriteUTF8FileAtomic(_path, content);
|
||||
}
|
36
src/cascadia/TerminalSettingsModel/BaseApplicationState.h
Normal file
36
src/cascadia/TerminalSettingsModel/BaseApplicationState.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- ApplicationState.h
|
||||
|
||||
Abstract:
|
||||
- TODO!
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
struct BaseApplicationState
|
||||
{
|
||||
BaseApplicationState(std::filesystem::path path) noexcept;
|
||||
~BaseApplicationState();
|
||||
|
||||
// Methods
|
||||
void Reload() const noexcept;
|
||||
|
||||
// General getters/setters
|
||||
winrt::hstring FilePath() const noexcept;
|
||||
|
||||
virtual void FromJson(const Json::Value& root) const noexcept = 0;
|
||||
virtual Json::Value ToJson() const noexcept = 0;
|
||||
|
||||
protected:
|
||||
virtual std::optional<std::string> _readFileContents() const;
|
||||
virtual void _writeFileContents(const std::string_view content) const;
|
||||
|
||||
void _write() const noexcept;
|
||||
void _read() const noexcept;
|
||||
|
||||
std::filesystem::path _path;
|
||||
til::throttled_func_trailing<> _throttler;
|
||||
};
|
134
src/cascadia/TerminalSettingsModel/ElevatedState.cpp
Normal file
134
src/cascadia/TerminalSettingsModel/ElevatedState.cpp
Normal file
|
@ -0,0 +1,134 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "ElevatedState.h"
|
||||
#include "CascadiaSettings.h"
|
||||
#include "ElevatedState.g.cpp"
|
||||
|
||||
#include "JsonUtils.h"
|
||||
#include "FileUtils.h"
|
||||
|
||||
#include <aclapi.h>
|
||||
|
||||
constexpr std::wstring_view stateFileName{ L"elevated-state.json" };
|
||||
|
||||
using namespace ::Microsoft::Terminal::Settings::Model;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
ElevatedState::ElevatedState(std::filesystem::path path) noexcept :
|
||||
BaseApplicationState{ path } {}
|
||||
|
||||
// Returns the application-global ElevatedState object.
|
||||
Microsoft::Terminal::Settings::Model::ElevatedState ElevatedState::SharedInstance()
|
||||
{
|
||||
// TODO! place in a totally different file! and path!
|
||||
static auto state = winrt::make_self<ElevatedState>(GetBaseSettingsPath() / stateFileName);
|
||||
state->Reload();
|
||||
|
||||
// const auto testPath{ GetBaseSettingsPath() / L"test.json" };
|
||||
|
||||
// PSID pEveryoneSID = NULL;
|
||||
// SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_NT_AUTHORITY;
|
||||
// BOOL success = AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &pEveryoneSID);
|
||||
|
||||
// EXPLICIT_ACCESS ea[1];
|
||||
// ZeroMemory(&ea, 1 * sizeof(EXPLICIT_ACCESS));
|
||||
// ea[0].grfAccessPermissions = KEY_READ;
|
||||
// ea[0].grfAccessMode = SET_ACCESS;
|
||||
// ea[0].grfInheritance = NO_INHERITANCE;
|
||||
// ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
// ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
|
||||
// ea[0].Trustee.ptstrName = (LPTSTR)pEveryoneSID;
|
||||
|
||||
// ACL acl;
|
||||
// PACL pAcl = &acl;
|
||||
// DWORD dwRes = SetEntriesInAcl(1, ea, NULL, &pAcl);
|
||||
// dwRes;
|
||||
|
||||
// SECURITY_DESCRIPTOR sd;
|
||||
// success = InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
|
||||
// success = SetSecurityDescriptorDacl(&sd,
|
||||
// TRUE, // bDaclPresent flag
|
||||
// pAcl,
|
||||
// FALSE);
|
||||
|
||||
// SECURITY_ATTRIBUTES sa;
|
||||
// // Initialize a security attributes structure.
|
||||
// sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
// sa.lpSecurityDescriptor = &sd;
|
||||
// sa.bInheritHandle = FALSE;
|
||||
// success;
|
||||
|
||||
// wil::unique_hfile file{ CreateFileW(testPath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) };
|
||||
// THROW_LAST_ERROR_IF(!file);
|
||||
|
||||
return *state;
|
||||
}
|
||||
|
||||
void ElevatedState::FromJson(const Json::Value& root) const noexcept
|
||||
{
|
||||
auto state = _state.lock();
|
||||
// GetValueForKey() comes in two variants:
|
||||
// * take a std::optional<T> reference
|
||||
// * return std::optional<T> by value
|
||||
// At the time of writing the former version skips missing fields in the json,
|
||||
// but we want to explicitly clear state fields that were removed from state.json.
|
||||
#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) state->name = JsonUtils::GetValueForKey<std::optional<type>>(root, key);
|
||||
MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN)
|
||||
#undef MTSM_ELEVATED_STATE_GEN
|
||||
}
|
||||
Json::Value ElevatedState::ToJson() const noexcept
|
||||
{
|
||||
Json::Value root{ Json::objectValue };
|
||||
|
||||
{
|
||||
auto state = _state.lock_shared();
|
||||
#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) JsonUtils::SetValueForKey(root, key, state->name);
|
||||
MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN)
|
||||
#undef MTSM_ELEVATED_STATE_GEN
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
// Generate all getter/setters
|
||||
#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) \
|
||||
type ElevatedState::name() const noexcept \
|
||||
{ \
|
||||
const auto state = _state.lock_shared(); \
|
||||
const auto& value = state->name; \
|
||||
return value ? *value : type{ __VA_ARGS__ }; \
|
||||
} \
|
||||
\
|
||||
void ElevatedState::name(const type& value) noexcept \
|
||||
{ \
|
||||
{ \
|
||||
auto state = _state.lock(); \
|
||||
state->name.emplace(value); \
|
||||
} \
|
||||
\
|
||||
_throttler(); \
|
||||
}
|
||||
MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN)
|
||||
#undef MTSM_ELEVATED_STATE_GEN
|
||||
|
||||
void ElevatedState::_writeFileContents(const std::string_view content) const
|
||||
{
|
||||
// DON'T use WriteUTF8FileAtomic, which will write to a temporary file
|
||||
// then rename that file to the final filename. That actually lets us
|
||||
// overwrite the elevate file's contents even when unelevated, because
|
||||
// we're effectively deleting the original file, then renaming a
|
||||
// different file in it's place.
|
||||
//
|
||||
// We're not worried about someone else doing that though, if they do
|
||||
// that with the wrong permissions, then we'll just ignore the file and
|
||||
// start over.
|
||||
WriteUTF8File(_path, content, true);
|
||||
}
|
||||
|
||||
std::optional<std::string> ElevatedState::_readFileContents() const
|
||||
{
|
||||
return ReadUTF8FileIfExists(_path, true);
|
||||
}
|
||||
}
|
60
src/cascadia/TerminalSettingsModel/ElevatedState.h
Normal file
60
src/cascadia/TerminalSettingsModel/ElevatedState.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- ElevatedState.h
|
||||
|
||||
Abstract:
|
||||
- If the CascadiaSettings class were AppData, then this class would be LocalAppData.
|
||||
Put anything in here that you wouldn't want to be stored next to user-editable settings.
|
||||
- Modify ElevatedState.idl and MTSM_ELEVATED_STATE_FIELDS to add new fields.
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "BaseApplicationState.h"
|
||||
#include "ElevatedState.g.h"
|
||||
#include <inc/cppwinrt_utils.h>
|
||||
|
||||
// This macro generates all getters and setters for ElevatedState.
|
||||
// It provides X with the following arguments:
|
||||
// (type, function name, JSON key, ...variadic construction arguments)
|
||||
#define MTSM_ELEVATED_STATE_FIELDS(X) \
|
||||
X(Windows::Foundation::Collections::IVector<hstring>, AllowedCommandlines, "allowedCommandlines")
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
struct ElevatedState : ElevatedStateT<ElevatedState>, public BaseApplicationState
|
||||
{
|
||||
static Microsoft::Terminal::Settings::Model::ElevatedState SharedInstance();
|
||||
|
||||
ElevatedState(std::filesystem::path path) noexcept;
|
||||
|
||||
void FromJson(const Json::Value& root) const noexcept override;
|
||||
Json::Value ToJson() const noexcept override;
|
||||
|
||||
// State getters/setters
|
||||
#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) \
|
||||
type name() const noexcept; \
|
||||
void name(const type& value) noexcept;
|
||||
MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN)
|
||||
#undef MTSM_ELEVATED_STATE_GEN
|
||||
|
||||
private:
|
||||
struct state_t
|
||||
{
|
||||
#define MTSM_ELEVATED_STATE_GEN(type, name, key, ...) std::optional<type> name{ __VA_ARGS__ };
|
||||
MTSM_ELEVATED_STATE_FIELDS(MTSM_ELEVATED_STATE_GEN)
|
||||
#undef MTSM_ELEVATED_STATE_GEN
|
||||
};
|
||||
til::shared_mutex<state_t> _state;
|
||||
|
||||
virtual std::optional<std::string> _readFileContents() const override;
|
||||
virtual void _writeFileContents(const std::string_view content) const override;
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(ElevatedState);
|
||||
}
|
15
src/cascadia/TerminalSettingsModel/ElevatedState.idl
Normal file
15
src/cascadia/TerminalSettingsModel/ElevatedState.idl
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Model
|
||||
{
|
||||
[default_interface] runtimeclass ElevatedState {
|
||||
static ElevatedState SharedInstance();
|
||||
|
||||
void Reload();
|
||||
|
||||
String FilePath { get; };
|
||||
|
||||
Windows.Foundation.Collections.IVector<String> AllowedCommandlines { get; set; };
|
||||
}
|
||||
}
|
|
@ -8,6 +8,8 @@
|
|||
#include <shlobj.h>
|
||||
#include <WtExeUtils.h>
|
||||
|
||||
#include <aclapi.h>
|
||||
|
||||
static constexpr std::string_view Utf8Bom{ u8"\uFEFF" };
|
||||
static constexpr std::wstring_view UnpackagedSettingsFolderName{ L"Microsoft\\Windows Terminal\\" };
|
||||
|
||||
|
@ -39,10 +41,89 @@ namespace Microsoft::Terminal::Settings::Model
|
|||
return baseSettingsPath;
|
||||
}
|
||||
|
||||
static bool _hasExpectedPermissions(const std::filesystem::path& path)
|
||||
{
|
||||
// If we want to only open the file if it's elevated, check the
|
||||
// permissions on this file. We want to make sure that:
|
||||
// * Everyone has permission to read
|
||||
// * admins can do anything
|
||||
// * no one else can do anything.
|
||||
PACL pAcl{ nullptr }; // This doesn't need to be cleanup up apparently
|
||||
|
||||
auto status = GetNamedSecurityInfo(path.c_str(),
|
||||
SE_FILE_OBJECT,
|
||||
DACL_SECURITY_INFORMATION,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&pAcl,
|
||||
nullptr,
|
||||
nullptr);
|
||||
THROW_IF_WIN32_ERROR(status);
|
||||
|
||||
PEXPLICIT_ACCESS pEA{ nullptr };
|
||||
DWORD count = 0;
|
||||
status = GetExplicitEntriesFromAcl(pAcl, &count, &pEA);
|
||||
THROW_IF_WIN32_ERROR(status);
|
||||
|
||||
auto explicitAccessCleanup = wil::scope_exit([&]() { ::LocalFree(pEA); });
|
||||
|
||||
if (count != 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now, get the Everyone and Admins SIDS so we can make sure they're
|
||||
// the ones in this file.
|
||||
|
||||
wil::unique_sid everyoneSid{};
|
||||
wil::unique_sid adminGroupSid{};
|
||||
SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;
|
||||
SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
|
||||
|
||||
// Create a SID for the BUILTIN\Administrators group.
|
||||
THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminGroupSid));
|
||||
|
||||
// Create a well-known SID for the Everyone group.
|
||||
THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &everyoneSid));
|
||||
|
||||
bool hadExpectedPermissions = true;
|
||||
|
||||
// Check that the permissions are what we'd expect them to be if only
|
||||
// admins can write to the file. This is basically a mirror of what we
|
||||
// set up in `WriteUTF8File`.
|
||||
|
||||
// For grfAccessPermissions, GENERIC_ALL turns into STANDARD_RIGHTS_ALL,
|
||||
// and GENERIC_READ -> READ_CONTROL
|
||||
hadExpectedPermissions = hadExpectedPermissions && WI_AreAllFlagsSet(pEA[0].grfAccessPermissions, STANDARD_RIGHTS_ALL);
|
||||
hadExpectedPermissions = hadExpectedPermissions && pEA[0].grfInheritance == NO_INHERITANCE;
|
||||
hadExpectedPermissions = hadExpectedPermissions && pEA[0].Trustee.TrusteeForm == TRUSTEE_IS_SID;
|
||||
// SIDs are void*'s that happen to convert to a wchar_t
|
||||
hadExpectedPermissions = hadExpectedPermissions && *(pEA[0].Trustee.ptstrName) == *(LPWSTR)(adminGroupSid.get());
|
||||
|
||||
// Now check the other EXPLICIT_ACCESS
|
||||
hadExpectedPermissions = hadExpectedPermissions && WI_IsFlagSet(pEA[1].grfAccessPermissions, READ_CONTROL);
|
||||
hadExpectedPermissions = hadExpectedPermissions && pEA[1].grfInheritance == NO_INHERITANCE;
|
||||
hadExpectedPermissions = hadExpectedPermissions && pEA[1].Trustee.TrusteeForm == TRUSTEE_IS_SID;
|
||||
hadExpectedPermissions = hadExpectedPermissions && *(pEA[1].Trustee.ptstrName) == *(LPWSTR)(everyoneSid.get());
|
||||
|
||||
return hadExpectedPermissions;
|
||||
}
|
||||
// Tries to read a file somewhat atomically without locking it.
|
||||
// Strips the UTF8 BOM if it exists.
|
||||
std::string ReadUTF8File(const std::filesystem::path& path)
|
||||
std::string ReadUTF8File(const std::filesystem::path& path, const bool elevatedOnly)
|
||||
{
|
||||
if (elevatedOnly)
|
||||
{
|
||||
const bool hadExpectedPermissions{ _hasExpectedPermissions(path) };
|
||||
if (!hadExpectedPermissions)
|
||||
{
|
||||
// delete the file. It's been compromised.
|
||||
LOG_LAST_ERROR_IF(!DeleteFile(path.c_str()));
|
||||
// Exit early, because obviously there's nothing to read from the deleted file.
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// From some casual observations we can determine that:
|
||||
// * ReadFile() always returns the requested amount of data (unless the file is smaller)
|
||||
// * It's unlikely that the file was changed between GetFileSize() and ReadFile()
|
||||
|
@ -89,11 +170,11 @@ namespace Microsoft::Terminal::Settings::Model
|
|||
}
|
||||
|
||||
// Same as ReadUTF8File, but returns an empty optional, if the file couldn't be opened.
|
||||
std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path)
|
||||
std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path, const bool elevatedOnly)
|
||||
{
|
||||
try
|
||||
{
|
||||
return { ReadUTF8File(path) };
|
||||
return { ReadUTF8File(path, elevatedOnly) };
|
||||
}
|
||||
catch (const wil::ResultException& exception)
|
||||
{
|
||||
|
@ -106,9 +187,75 @@ namespace Microsoft::Terminal::Settings::Model
|
|||
}
|
||||
}
|
||||
|
||||
void WriteUTF8File(const std::filesystem::path& path, const std::string_view content)
|
||||
void WriteUTF8File(const std::filesystem::path& path,
|
||||
const std::string_view content,
|
||||
const bool elevatedOnly)
|
||||
{
|
||||
wil::unique_hfile file{ CreateFileW(path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) };
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
if (elevatedOnly)
|
||||
{
|
||||
// This is very vaguely taken from
|
||||
// https://docs.microsoft.com/en-us/windows/win32/secauthz/creating-a-security-descriptor-for-a-new-object-in-c--
|
||||
// With using https://docs.microsoft.com/en-us/windows/win32/secauthz/well-known-sids
|
||||
// to find out that
|
||||
// * SECURITY_NT_AUTHORITY+SECURITY_LOCAL_SYSTEM_RID == NT AUTHORITY\SYSTEM
|
||||
// * SECURITY_NT_AUTHORITY+SECURITY_BUILTIN_DOMAIN_RID+DOMAIN_ALIAS_RID_ADMINS == BUILTIN\Administrators
|
||||
// * SECURITY_WORLD_SID_AUTHORITY+SECURITY_WORLD_RID == Everyone
|
||||
//
|
||||
// Raymond Chen recommended that I make this file only writable by
|
||||
// SYSTEM, but if I did that, then even we can't write the file
|
||||
// while elevated, which isn't what we want.
|
||||
|
||||
wil::unique_sid everyoneSid{};
|
||||
wil::unique_sid adminGroupSid{};
|
||||
SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;
|
||||
SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
|
||||
|
||||
// Create a SID for the BUILTIN\Administrators group.
|
||||
THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &adminGroupSid));
|
||||
|
||||
// Create a well-known SID for the Everyone group.
|
||||
THROW_IF_WIN32_BOOL_FALSE(AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &everyoneSid));
|
||||
|
||||
EXPLICIT_ACCESS ea[2];
|
||||
ZeroMemory(&ea, 2 * sizeof(EXPLICIT_ACCESS));
|
||||
// Grant Admins all permissions on this file
|
||||
ea[0].grfAccessPermissions = GENERIC_ALL;
|
||||
ea[0].grfAccessMode = SET_ACCESS;
|
||||
ea[0].grfInheritance = NO_INHERITANCE;
|
||||
ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
|
||||
ea[0].Trustee.ptstrName = (LPWSTR)(adminGroupSid.get());
|
||||
|
||||
// Grant Everyone the permission or read this file
|
||||
ea[1].grfAccessPermissions = GENERIC_READ;
|
||||
ea[1].grfAccessMode = SET_ACCESS;
|
||||
ea[1].grfInheritance = NO_INHERITANCE;
|
||||
ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
ea[1].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
|
||||
ea[1].Trustee.ptstrName = (LPWSTR)(everyoneSid.get());
|
||||
|
||||
ACL acl;
|
||||
PACL pAcl = &acl;
|
||||
THROW_IF_WIN32_ERROR(SetEntriesInAcl(2, ea, nullptr, &pAcl));
|
||||
|
||||
SECURITY_DESCRIPTOR sd;
|
||||
THROW_IF_WIN32_BOOL_FALSE(InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION));
|
||||
THROW_IF_WIN32_BOOL_FALSE(SetSecurityDescriptorDacl(&sd, true, pAcl, false));
|
||||
|
||||
// Initialize a security attributes structure.
|
||||
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
sa.lpSecurityDescriptor = &sd;
|
||||
sa.bInheritHandle = false;
|
||||
}
|
||||
|
||||
wil::unique_hfile file{ CreateFileW(path.c_str(),
|
||||
GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
elevatedOnly ? &sa : nullptr,
|
||||
CREATE_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
nullptr) };
|
||||
THROW_LAST_ERROR_IF(!file);
|
||||
|
||||
const auto fileSize = gsl::narrow<DWORD>(content.size());
|
||||
|
@ -121,7 +268,8 @@ namespace Microsoft::Terminal::Settings::Model
|
|||
}
|
||||
}
|
||||
|
||||
void WriteUTF8FileAtomic(const std::filesystem::path& path, const std::string_view content)
|
||||
void WriteUTF8FileAtomic(const std::filesystem::path& path,
|
||||
const std::string_view content)
|
||||
{
|
||||
// GH#10787: rename() will replace symbolic links themselves and not the path they point at.
|
||||
// It's thus important that we first resolve them before generating temporary path.
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
namespace Microsoft::Terminal::Settings::Model
|
||||
{
|
||||
std::filesystem::path GetBaseSettingsPath();
|
||||
std::string ReadUTF8File(const std::filesystem::path& path);
|
||||
std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path);
|
||||
void WriteUTF8File(const std::filesystem::path& path, const std::string_view content);
|
||||
std::string ReadUTF8File(const std::filesystem::path& path, const bool elevatedOnly = false);
|
||||
std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path, const bool elevatedOnly = false);
|
||||
void WriteUTF8File(const std::filesystem::path& path, const std::string_view content, const bool elevatedOnly = false);
|
||||
void WriteUTF8FileAtomic(const std::filesystem::path& path, const std::string_view content);
|
||||
}
|
||||
|
|
|
@ -382,7 +382,6 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils
|
|||
};
|
||||
|
||||
template<typename T>
|
||||
|
||||
struct ConversionTrait<std::unordered_map<std::string, T>>
|
||||
{
|
||||
std::unordered_map<std::string, T> FromJson(const Json::Value& json) const
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
<ClInclude Include="IconPathConverter.h">
|
||||
<DependentUpon>IconPathConverter.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="BaseApplicationState.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="ActionArgs.h">
|
||||
<DependentUpon>ActionArgs.idl</DependentUpon>
|
||||
|
@ -35,6 +36,9 @@
|
|||
<ClInclude Include="ApplicationState.h">
|
||||
<DependentUpon>ApplicationState.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ElevatedState.h">
|
||||
<DependentUpon>ElevatedState.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="CascadiaSettings.h">
|
||||
<DependentUpon>CascadiaSettings.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
|
@ -88,6 +92,7 @@
|
|||
<DependentUpon>IconPathConverter.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="init.cpp" />
|
||||
<ClCompile Include="BaseApplicationState.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
|
@ -107,6 +112,9 @@
|
|||
<ClCompile Include="ApplicationState.cpp">
|
||||
<DependentUpon>ApplicationState.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ElevatedState.cpp">
|
||||
<DependentUpon>ElevatedState.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CascadiaSettings.cpp">
|
||||
<DependentUpon>CascadiaSettings.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
|
@ -156,6 +164,7 @@
|
|||
<Midl Include="ActionArgs.idl" />
|
||||
<Midl Include="ActionMap.idl" />
|
||||
<Midl Include="ApplicationState.idl" />
|
||||
<Midl Include="ElevatedState.idl" />
|
||||
<Midl Include="CascadiaSettings.idl" />
|
||||
<Midl Include="ColorScheme.idl" />
|
||||
<Midl Include="Command.idl" />
|
||||
|
|
|
@ -53,3 +53,6 @@ TRACELOGGING_DECLARE_PROVIDER(g_hSettingsModelProvider);
|
|||
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#include "til.h"
|
||||
|
||||
#include <til/mutex.h>
|
||||
#include <til/throttled_func.h>
|
||||
|
|
Loading…
Reference in a new issue