diff --git a/NetworkServer.cpp b/NetworkServer.cpp index e1068d4f4..b026e11ca 100644 --- a/NetworkServer.cpp +++ b/NetworkServer.cpp @@ -2560,7 +2560,7 @@ NetPacketStatus NetworkServer::ProcessRequest_SettingsManager_ModifySettings(Net settings_json_str.assign((char*)data_ptr, data_size); settings_json_str = StringUtils::remove_null_terminating_chars(settings_json_str); - settings_manager->ModifySettingsFromJsonString(settings_json_str); + settings_manager->ModifySettingsFromJsonString(settings_json_str, true); return(NET_PACKET_STATUS_OK); } @@ -2592,7 +2592,7 @@ NetPacketStatus NetworkServer::ProcessRequest_SettingsManager_SetSettings(Networ settings_json_str.assign((char*)data_ptr, data_size); settings_json_str = StringUtils::remove_null_terminating_chars(settings_json_str); - settings_manager->SetSettingsFromJsonString(settings_json_str); + settings_manager->SetSettingsFromJsonString(settings_json_str, true); return(NET_PACKET_STATUS_OK); } diff --git a/ProfileManager.cpp b/ProfileManager.cpp index 83be6ca55..d48baa0bf 100644 --- a/ProfileManager.cpp +++ b/ProfileManager.cpp @@ -64,7 +64,7 @@ ProfileManager::ProfileManager(const filesystem::path& config_dir) profilemanager_settings_schema["suspend_profile"]["type"] = "profile"; profilemanager_settings_schema["suspend_profile"]["description"] = QT_TRANSLATE_NOOP("Settings", "Profile to load before system enters sleep mode"); - settings_manager->RegisterSettingsSchema("ProfileManager", QT_TRANSLATE_NOOP("Settings", "Profile Manager"), profilemanager_settings_schema, 1); + settings_manager->RegisterSettingsSchemaOrder("ProfileManager", QT_TRANSLATE_NOOP("Settings", "Profile Manager"), profilemanager_settings_schema, 1); /*-----------------------------------------------------*\ | Read in profile manager settings and initialize any | diff --git a/ResourceManager.cpp b/ResourceManager.cpp index 8c69d5690..135813c0d 100644 --- a/ResourceManager.cpp +++ b/ResourceManager.cpp @@ -118,7 +118,7 @@ static void ResourceManagerNetworkClientCallback(void* this_ptr, unsigned int up case NETWORKCLIENT_UPDATE_REASON_PROFILEMANAGER_ACTIVE_PROFILE_CHANGED: this_obj->GetProfileManager()->SignalProfileManagerUpdate(PROFILEMANAGER_UPDATE_REASON_ACTIVE_PROFILE_CHANGED); break; - + case NETWORKCLIENT_UPDATE_REASON_SERVER_FLAGS_RECEIVED: if(this_obj->IsLocalClient()) { @@ -263,7 +263,7 @@ ResourceManager::ResourceManager() server_settings_schema["legacy_workaround"]["type"] = "bool"; server_settings_schema["legacy_workaround"]["description"] = QT_TRANSLATE_NOOP("Settings", "Workaround for some older SDK implementations that sent incorrect packet size for certain packets"); - settings_manager->RegisterSettingsSchema("Server", QT_TRANSLATE_NOOP("Settings", "Server"), server_settings_schema); + settings_manager->RegisterSettingsSchemaLocalOnly("Server", QT_TRANSLATE_NOOP("Settings", "Server"), server_settings_schema); /*-----------------------------------------------------*\ | Configure the log manager | diff --git a/SettingsManager.cpp b/SettingsManager.cpp index e47217902..5f54fc43c 100644 --- a/SettingsManager.cpp +++ b/SettingsManager.cpp @@ -27,12 +27,92 @@ const char* SETTINGSMANAGER = "SettingsManager"; static const std::string ui_settings_keys[4] = { - "UserInterface", - "AutoStart", "Plugins", "Client", }; +/*---------------------------------------------------------*\ +| Schema Validation Static Helper | +\*---------------------------------------------------------*/ +static bool SettingsValueMatchesType(const json& value, const std::string& schema_type) +{ + /*-----------------------------------------------------*\ + | Map schema type strings to nlohmann::json type checks | + \------------------------------------------------------*/ + if(schema_type == "bool") + { + return value.is_boolean(); + } + + if(schema_type == "integer") + { + return value.is_number_integer(); + } + + if(schema_type == "number") + { + return value.is_number(); + } + + if(schema_type == "string") + { + return value.is_string(); + } + + if(schema_type == "array") + { + return value.is_array(); + } + + if(schema_type == "object") + { + return value.is_object(); + } + + /*-----------------------------------------------------*\ + | Custom OpenRGB type aliases | + \------------------------------------------------------*/ + if(schema_type == "language") + { + return value.is_string(); + } + + if(schema_type == "profile") + { + return value.is_object(); + } + + /*-----------------------------------------------------*\ + | Unknown type string | + \------------------------------------------------------*/ + LOG_WARNING("[%s] Unknown schema type \"%s\"", SETTINGSMANAGER, schema_type.c_str()); + return false; +} + +/*---------------------------------------------------------*\ +| Check if setting schema has local_only parameter | +\*---------------------------------------------------------*/ +static bool IsLocalOnlySetting(const nlohmann::json& settings_schema, const std::string& settings_key) +{ + /*-----------------------------------------------------*\ + | Check if the schema exists for this settings_key | + \------------------------------------------------------*/ + if(!settings_schema.contains(settings_key)) + { + return false; + } + + /*-----------------------------------------------------*\ + | Check if the schema has local_only set to true | + \------------------------------------------------------*/ + if(settings_schema[settings_key].contains("local_only")) + { + return settings_schema[settings_key]["local_only"].get(); + } + + return false; +} + SettingsManager::SettingsManager() { config_found = false; @@ -46,18 +126,18 @@ SettingsManager::~SettingsManager() json SettingsManager::GetSettings(std::string settings_key) { json result; - bool ui_settings_key = false; + bool local_setting = IsLocalOnlySetting(settings_schema, settings_key); for(std::size_t settings_key_idx = 0; settings_key_idx < 4; settings_key_idx++) { if(settings_key == ui_settings_keys[settings_key_idx]) { - ui_settings_key = true; + local_setting = true; break; } } - if(!ui_settings_key && ResourceManager::get()->IsLocalClient() && (ResourceManager::get()->GetLocalClient()->GetSupportsSettingsManagerAPI())) + if(!local_setting && ResourceManager::get()->IsLocalClient() && (ResourceManager::get()->GetLocalClient()->GetSupportsSettingsManagerAPI())) { /*-------------------------------------------------*\ | If this is a local client, request the settings | @@ -104,14 +184,15 @@ json SettingsManager::GetSettingsSchema(std::string settings_key) void SettingsManager::RegisterSettingsSchema(std::string settings_key, std::string settings_title, json& new_schema) { - RegisterSettingsSchema(settings_key, settings_title, new_schema, -1); + RegisterSettingsSchemaComplete(settings_key, settings_title, new_schema, -1, false); } -void SettingsManager::RegisterSettingsSchema(std::string settings_key, std::string settings_title, json& new_schema, int order) +void SettingsManager::RegisterSettingsSchemaComplete(std::string settings_key, std::string settings_title, json& new_schema, int order, bool local_only) { - settings_schema[settings_key]["title"] = settings_title; - settings_schema[settings_key]["type"] = "object"; + settings_schema[settings_key]["title"] = settings_title; + settings_schema[settings_key]["type"] = "object"; settings_schema[settings_key]["properties"].update(new_schema, true); + settings_schema[settings_key]["local_only"] = local_only; if(order >= 0) { @@ -121,20 +202,30 @@ void SettingsManager::RegisterSettingsSchema(std::string settings_key, std::stri SignalSettingsManagerUpdate(SETTINGSMANAGER_UPDATE_REASON_SETTINGS_SCHEMA_UPDATED); } -void SettingsManager::ModifySettings(std::string settings_key, json new_settings) +void SettingsManager::RegisterSettingsSchemaLocalOnly(std::string settings_key, std::string settings_title, json& new_schema) { - bool ui_settings_key = false; + RegisterSettingsSchemaComplete(settings_key, settings_title, new_schema, -1, true); +} + +void SettingsManager::RegisterSettingsSchemaOrder(std::string settings_key, std::string settings_title, json& new_schema, int order) +{ + RegisterSettingsSchemaComplete(settings_key, settings_title, new_schema, order, false); +} + +void SettingsManager::ModifySettings(std::string settings_key, json new_settings, bool from_server) +{ + bool local_setting = IsLocalOnlySetting(settings_schema, settings_key); for(std::size_t settings_key_idx = 0; settings_key_idx < 4; settings_key_idx++) { if(settings_key == ui_settings_keys[settings_key_idx]) { - ui_settings_key = true; + local_setting = true; break; } } - if(!ui_settings_key && ResourceManager::get()->IsLocalClient() && (ResourceManager::get()->GetLocalClient()->GetSupportsSettingsManagerAPI())) + if(!local_setting && ResourceManager::get()->IsLocalClient() && (ResourceManager::get()->GetLocalClient()->GetSupportsSettingsManagerAPI())) { /*-------------------------------------------------*\ | If this is a local client, request the settings | @@ -146,17 +237,18 @@ void SettingsManager::ModifySettings(std::string settings_key, json new_settings ResourceManager::get()->GetLocalClient()->SettingsManager_ModifySettings(settings_json.dump()); } - else + else if(!from_server) { mutex.lock(); - settings_data[settings_key].update(new_settings, true); + json filtered_settings = FilterSettingsAgainstSchema(settings_key, new_settings); + settings_data[settings_key].update(filtered_settings, true); mutex.unlock(); } SignalSettingsManagerUpdate(SETTINGSMANAGER_UPDATE_REASON_SETTINGS_UPDATED); } -void SettingsManager::ModifySettingsFromJsonString(std::string settings_json_str) +void SettingsManager::ModifySettingsFromJsonString(std::string settings_json_str, bool from_server) { /*-----------------------------------------------------*\ | Parse the JSON string | @@ -176,20 +268,20 @@ void SettingsManager::ModifySettingsFromJsonString(std::string settings_json_str } } -void SettingsManager::SetSettings(std::string settings_key, json new_settings) +void SettingsManager::SetSettings(std::string settings_key, json new_settings, bool from_server) { - bool ui_settings_key = false; + bool local_setting = IsLocalOnlySetting(settings_schema, settings_key); for(std::size_t settings_key_idx = 0; settings_key_idx < 4; settings_key_idx++) { if(settings_key == ui_settings_keys[settings_key_idx]) { - ui_settings_key = true; + local_setting = true; break; } } - if(!ui_settings_key && ResourceManager::get()->IsLocalClient() && (ResourceManager::get()->GetLocalClient()->GetSupportsSettingsManagerAPI())) + if(!local_setting && ResourceManager::get()->IsLocalClient() && (ResourceManager::get()->GetLocalClient()->GetSupportsSettingsManagerAPI())) { /*-------------------------------------------------*\ | If this is a local client, request the settings | @@ -201,17 +293,18 @@ void SettingsManager::SetSettings(std::string settings_key, json new_settings) ResourceManager::get()->GetLocalClient()->SettingsManager_SetSettings(settings_json.dump()); } - else + else if(!from_server) { mutex.lock(); - settings_data[settings_key] = new_settings; + json filtered_settings = FilterSettingsAgainstSchema(settings_key, new_settings); + settings_data[settings_key] = filtered_settings; mutex.unlock(); } SignalSettingsManagerUpdate(SETTINGSMANAGER_UPDATE_REASON_SETTINGS_UPDATED); } -void SettingsManager::SetSettingsFromJsonString(std::string settings_json_str) +void SettingsManager::SetSettingsFromJsonString(std::string settings_json_str, bool from_server) { /*-----------------------------------------------------*\ | Parse the JSON string | @@ -378,3 +471,83 @@ void SettingsManager::SignalSettingsManagerUpdate(unsigned int update_reason) LOG_TRACE("[%s] SettingsManager update signalled: %d", SETTINGSMANAGER, update_reason); } + +/*---------------------------------------------------------*\ +| Schema Validation | +\*---------------------------------------------------------*/ +json SettingsManager::FilterSettingsAgainstSchema(std::string settings_key, json new_settings) +{ + json filtered; + + /*------------------------------------------------------* + | If new_settings is not an object, there is nothing | + | to filter. Return an empty object. | + \------------------------------------------------------*/ + if(!new_settings.is_object()) + { + LOG_WARNING("[%s] Settings for key \"%s\" is not a JSON object, nothing will be stored", SETTINGSMANAGER, settings_key.c_str()); + return filtered; + } + + /*-----------------------------------------------------*\ + | Check if a schema is registered for this settings_key | + | If not, return without filtering. | + \------------------------------------------------------*/ + if(!settings_schema.contains(settings_key)) + { + return new_settings; + } + + /*-----------------------------------------------------*\ + | Check that this schema has properties. | + | If not, return an empty object. | + \------------------------------------------------------*/ + if(!settings_schema[settings_key].contains("properties")) + { + LOG_WARNING("[%s] Schema for settings key \"%s\" missing the properties key, nothing will be stored", SETTINGSMANAGER, settings_key.c_str()); + return filtered; + } + + json& schema_properties = settings_schema[settings_key]["properties"]; + + /*-----------------------------------------------------*\ + | Iterate through each entry in new_settings and check | + | it against the schema properties. This use of | + | `auto` is acceptable due to how the JSON library | + | implements iterators, the type would change based on | + | the library version. | + \------------------------------------------------------*/ + for(auto& element : new_settings.items()) + { + std::string key = element.key(); + json& value = element.value(); + + /*-------------------------------------------------*\ + | Check if the key exists in the schema properties | + \*-------------------------------------------------*/ + if(!schema_properties.contains(key)) + { + LOG_WARNING("[%s] Settings key \"%s\" not found in schema for \"%s\", skipping", SETTINGSMANAGER, key.c_str(), settings_key.c_str()); + continue; + } + + /*-------------------------------------------------*\ + | Check if the value type matches the schema's | + | declared type | + \*-------------------------------------------------*/ + if(schema_properties[key].contains("type")) + { + std::string schema_type = schema_properties[key]["type"]; + + if(!SettingsValueMatchesType(value, schema_type)) + { + LOG_WARNING("[%s] Settings key \"%s\" has incorrect type (expected %s), skipping", SETTINGSMANAGER, key.c_str(), schema_type.c_str()); + continue; + } + } + + filtered[key] = value; + } + + return filtered; +} diff --git a/SettingsManager.h b/SettingsManager.h index afcd4bd5c..4fd4dad5b 100644 --- a/SettingsManager.h +++ b/SettingsManager.h @@ -39,15 +39,15 @@ enum class SettingsManagerInterface { public: - virtual nlohmann::json GetSettings(std::string settings_key) = 0; - virtual nlohmann::json GetSettingsSchema(std::string settings_key) = 0; - virtual void ModifySettings(std::string settings_key, nlohmann::json new_settings) = 0; - virtual void ModifySettingsFromJsonString(std::string settings_json_str) = 0; - virtual void SetSettings(std::string settings_key, nlohmann::json new_settings) = 0; - virtual void SetSettingsFromJsonString(std::string settings_json_str) = 0; + virtual nlohmann::json GetSettings(std::string settings_key) = 0; + virtual nlohmann::json GetSettingsSchema(std::string settings_key) = 0; + virtual void ModifySettings(std::string settings_key, nlohmann::json new_settings, bool from_server) = 0; + virtual void ModifySettingsFromJsonString(std::string settings_json_str, bool from_server) = 0; + virtual void SetSettings(std::string settings_key, nlohmann::json new_settings, bool from_server) = 0; + virtual void SetSettingsFromJsonString(std::string settings_json_str, bool from_server) = 0; - virtual void LoadSettings(const filesystem::path& filename) = 0; - virtual void SaveSettings() = 0; + virtual void LoadSettings(const filesystem::path& filename) = 0; + virtual void SaveSettings() = 0; protected: virtual ~SettingsManagerInterface() {}; @@ -62,11 +62,13 @@ public: nlohmann::json GetSettings(std::string settings_key); nlohmann::json GetSettingsSchema(std::string settings_key); void RegisterSettingsSchema(std::string settings_key, std::string settings_title, nlohmann::json& new_schema); - void RegisterSettingsSchema(std::string settings_key, std::string settings_title, nlohmann::json& new_schema, int order); - void ModifySettings(std::string settings_key, nlohmann::json new_settings); - void ModifySettingsFromJsonString(std::string settings_json_str); - void SetSettings(std::string settings_key, nlohmann::json new_settings); - void SetSettingsFromJsonString(std::string settings_json_str); + void RegisterSettingsSchemaComplete(std::string settings_key, std::string settings_title, nlohmann::json& new_schema, int order, bool local_only); + void RegisterSettingsSchemaLocalOnly(std::string settings_key, std::string settings_title, nlohmann::json& new_schema); + void RegisterSettingsSchemaOrder(std::string settings_key, std::string settings_title, nlohmann::json& new_schema, int order); + void ModifySettings(std::string settings_key, nlohmann::json new_settings, bool from_server = false); + void ModifySettingsFromJsonString(std::string settings_json_str, bool from_server = false); + void SetSettings(std::string settings_key, nlohmann::json new_settings, bool from_server = false); + void SetSettingsFromJsonString(std::string settings_json_str, bool from_server = false); void LoadSettings(const filesystem::path& filename); void SaveSettings(); @@ -107,4 +109,9 @@ private: std::vector SettingsManagerCallbacks; std::vector SettingsManagerCallbackArgs; std::mutex SettingsManagerCallbackMutex; + + /*-----------------------------------------------------*\ + | Schema Validation | + \*-----------------------------------------------------*/ + nlohmann::json FilterSettingsAgainstSchema(std::string settings_key, nlohmann::json new_settings); }; diff --git a/qt/OpenRGBDialog/OpenRGBDialog.cpp b/qt/OpenRGBDialog/OpenRGBDialog.cpp index 85b2e88bf..bcaf8dccd 100644 --- a/qt/OpenRGBDialog/OpenRGBDialog.cpp +++ b/qt/OpenRGBDialog/OpenRGBDialog.cpp @@ -175,7 +175,7 @@ OpenRGBDialog::OpenRGBDialog(QWidget *parent) : QMainWindow(parent), ui(new Ui:: autostart_settings_schema["custom_arguments"]["description"] = QT_TRANSLATE_NOOP("Settings", "Additional command line arguments to pass to OpenRGB when starting on login"); autostart_settings_schema["custom_arguments"]["order"] = 2; - ResourceManager::get()->GetSettingsManager()->RegisterSettingsSchema("AutoStart", QT_TRANSLATE_NOOP("Settings", "Start at Login"), autostart_settings_schema, 2); + ResourceManager::get()->GetSettingsManager()->RegisterSettingsSchemaComplete("AutoStart", QT_TRANSLATE_NOOP("Settings", "Start at Login"), autostart_settings_schema, 2, true); /*-----------------------------------------------------*\ | Create UserInterface settings schema | @@ -260,7 +260,7 @@ OpenRGBDialog::OpenRGBDialog(QWidget *parent) : QMainWindow(parent), ui(new Ui:: ui_settings_schema["geometry"]["properties"]["height"]["type"] = "integer"; ui_settings_schema["geometry"]["properties"]["height"]["order"] = 5; - ResourceManager::get()->GetSettingsManager()->RegisterSettingsSchema("UserInterface", QT_TRANSLATE_NOOP("Settings", "User Interface"), ui_settings_schema, 0); + ResourceManager::get()->GetSettingsManager()->RegisterSettingsSchemaComplete("UserInterface", QT_TRANSLATE_NOOP("Settings", "User Interface"), ui_settings_schema, 0, true); #if defined(_WIN32) || defined(_MACOSX_X86_X64) /*-----------------------------------------------------*\