From d02d6b5870eafb6f9951ef2b612337ecf78534df Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 31 May 2025 18:57:40 +0200 Subject: [PATCH] Add enterprise config support via env vars & Windows registry --- app/MindWork AI Studio/Program.cs | 24 +++++ .../Tools/Services/RustService.Enterprise.cs | 44 +++++++++ runtime/Cargo.lock | 32 ++++++- runtime/Cargo.toml | 4 + runtime/src/environment.rs | 96 +++++++++++++++++++ runtime/src/runtime_api.rs | 2 + 6 files changed, 197 insertions(+), 5 deletions(-) create mode 100644 app/MindWork AI Studio/Tools/Services/RustService.Enterprise.cs diff --git a/app/MindWork AI Studio/Program.cs b/app/MindWork AI Studio/Program.cs index 1630a7a7..81b25fa7 100644 --- a/app/MindWork AI Studio/Program.cs +++ b/app/MindWork AI Studio/Program.cs @@ -208,6 +208,30 @@ internal sealed class Program await rust.AppIsReady(); programLogger.LogInformation("The AI Studio server is ready."); + // + // Read the enterprise environment for the current user's configuration: + // + var enterpriseConfigServerUrl = await RUST_SERVICE.EnterpriseEnvConfigServerUrl(); + var enterpriseConfigId = await RUST_SERVICE.EnterpriseEnvConfigId(); + switch (enterpriseConfigServerUrl) + { + case null when enterpriseConfigId == Guid.Empty: + programLogger.LogInformation("AI Studio runs without an enterprise configuration."); + break; + + case null: + programLogger.LogWarning($"AI Studio runs with an enterprise configuration id ('{enterpriseConfigId}'), but the configuration server URL is not set."); + break; + + case not null when enterpriseConfigId == Guid.Empty: + programLogger.LogWarning($"AI Studio runs with an enterprise configuration server URL ('{enterpriseConfigServerUrl}'), but the configuration ID is not set."); + break; + + default: + programLogger.LogInformation($"AI Studio runs with an enterprise configuration id ('{enterpriseConfigId}') and configuration server URL ('{enterpriseConfigServerUrl}')."); + break; + } + TaskScheduler.UnobservedTaskException += (sender, taskArgs) => { programLogger.LogError(taskArgs.Exception, $"Unobserved task exception by sender '{sender ?? "n/a"}'."); diff --git a/app/MindWork AI Studio/Tools/Services/RustService.Enterprise.cs b/app/MindWork AI Studio/Tools/Services/RustService.Enterprise.cs new file mode 100644 index 00000000..6fae98af --- /dev/null +++ b/app/MindWork AI Studio/Tools/Services/RustService.Enterprise.cs @@ -0,0 +1,44 @@ +namespace AIStudio.Tools.Services; + +public sealed partial class RustService +{ + /// + /// Tries to read the enterprise environment for the current user's configuration ID. + /// + /// + /// Returns the empty Guid when the environment is not set or the request fails. + /// Otherwise, the configuration ID. + /// + public async Task EnterpriseEnvConfigId() + { + var result = await this.http.GetAsync("/system/enterprise/config/id"); + if (!result.IsSuccessStatusCode) + { + this.logger!.LogError($"Failed to query the enterprise configuration ID: '{result.StatusCode}'"); + return Guid.Empty; + } + + Guid.TryParse(await result.Content.ReadAsStringAsync(), out var configurationId); + return configurationId; + } + + /// + /// Tries to read the enterprise environment for the current user's configuration server URL. + /// + /// + /// Returns null when the environment is not set or the request fails. + /// Otherwise, the configuration server URL. + /// + public async Task EnterpriseEnvConfigServerUrl() + { + var result = await this.http.GetAsync("/system/enterprise/config/server"); + if (!result.IsSuccessStatusCode) + { + this.logger!.LogError($"Failed to query the enterprise configuration server URL: '{result.StatusCode}'"); + return null; + } + + var serverUrl = await result.Content.ReadAsStringAsync(); + return string.IsNullOrWhiteSpace(serverUrl) ? null : serverUrl; + } +} \ No newline at end of file diff --git a/runtime/Cargo.lock b/runtime/Cargo.lock index e6db9da3..c6375c26 100644 --- a/runtime/Cargo.lock +++ b/runtime/Cargo.lock @@ -2632,6 +2632,7 @@ dependencies = [ "base64 0.22.1", "calamine", "cbc", + "cfg-if", "cipher", "crossbeam-channel", "file-format", @@ -2660,6 +2661,7 @@ dependencies = [ "tokio", "tokio-stream", "url", + "windows-registry 0.5.2", ] [[package]] @@ -3978,7 +3980,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows-registry", + "windows-registry 0.4.0", ] [[package]] @@ -5949,15 +5951,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ "windows-result", - "windows-strings", + "windows-strings 0.3.1", "windows-targets 0.53.0", ] [[package]] -name = "windows-result" -version = "0.3.2" +name = "windows-registry" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] @@ -5971,6 +5984,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.42.0" diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 1687f162..6bb18649 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -37,6 +37,7 @@ file-format = "0.27.0" calamine = "0.27.0" pdfium-render = "0.8.31" sys-locale = "0.3.2" +cfg-if = "1.0.0" # Fixes security vulnerability downstream, where the upstream is not fixed yet: url = "2.5" @@ -50,5 +51,8 @@ reqwest = { version = "0.12.15", features = ["native-tls-vendored"] } # Fixes security vulnerability downstream, where the upstream is not fixed yet: openssl = "0.10.72" +[target.'cfg(target_os = "windows")'.dependencies] +windows-registry = "0.5.2" + [features] custom-protocol = ["tauri/custom-protocol"] diff --git a/runtime/src/environment.rs b/runtime/src/environment.rs index af3435b1..dbcf7361 100644 --- a/runtime/src/environment.rs +++ b/runtime/src/environment.rs @@ -1,4 +1,6 @@ +use std::env; use std::sync::OnceLock; +use log::info; use rocket::get; use sys_locale::get_locale; use crate::api_token::APIToken; @@ -43,4 +45,98 @@ pub fn read_user_language(_token: APIToken) -> String { log::warn!("Could not determine the system language. Use default 'en-US'."); String::from("en-US") }) +} + +#[get("/system/enterprise/config/id")] +pub fn read_enterprise_env_config_id(_token: APIToken) -> Option { + // + // When we are on a Windows machine, we try to read the enterprise config from + // the Windows registry. In case we can't find the registry key, or we are on a + // macOS or Linux machine, we try to read the enterprise config from the + // environment variables. + // + // The registry key is: + // HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT + // + // In this registry key, we expect the following values: + // - config_id + // + // The environment variable is: + // MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ID + // + get_enterprise_configuration( + "config_id", + "MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ID", + ) +} + +#[get("/system/enterprise/config/server")] +pub fn read_enterprise_env_config_server_url(_token: APIToken) -> Option { + // + // When we are on a Windows machine, we try to read the enterprise config from + // the Windows registry. In case we can't find the registry key, or we are on a + // macOS or Linux machine, we try to read the enterprise config from the + // environment variables. + // + // The registry key is: + // HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT + // + // In this registry key, we expect the following values: + // - config_server_url + // + // The environment variable is: + // MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_SERVER_URL + // + get_enterprise_configuration( + "config_server_url", + "MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_SERVER_URL", + ) +} + +fn get_enterprise_configuration(reg_value: &str, env_name: &str) -> Option { + info!("Trying to read the enterprise environment for some predefined configuration."); + cfg_if::cfg_if! { + if #[cfg(target_os = "windows")] { + info!(r"Detected a Windows machine, trying to read the registry key 'HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT' or the environment variables"); + use windows_registry::*; + let key_path = r"Software\github\MindWork AI Studio\Enterprise IT"; + let key = match CURRENT_USER.open(key_path) { + Ok(key) => key, + Err(_) => { + info!(r"Could not read the registry key HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT. Falling back to environment variables."); + return match env::var(env_name) { + Ok(val) => { + info!("Falling back to the environment variable '{}' was successful.", env_name); + Some(val) + }, + Err(_) => { + info!("Falling back to the environment variable '{}' was not successful. It appears that this is not an enterprise environment.", env_name); + None + }, + } + }, + }; + + match key.get_string(reg_value) { + Ok(val) => Some(val), + Err(_) => { + info!(r"We could read the registry key 'HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT', but the value '{}' could not be read. Falling back to environment variables.", reg_value); + match env::var(env_name) { + Ok(val) => { + info!("Falling back to the environment variable '{}' was successful.", env_name); + Some(val) + }, + Err(_) => { + info!("Falling back to the environment variable '{}' was not successful. It appears that this is not an enterprise environment.", env_name); + None + } + } + }, + } + } else { + // In the case of macOS or Linux, we just read the environment variable: + info!(r"Detected a Unix machine, trying to read the environment variable '{}'.", env_name) + env::var(env_name).ok() + } + } } \ No newline at end of file diff --git a/runtime/src/runtime_api.rs b/runtime/src/runtime_api.rs index 459fc936..a90b43f1 100644 --- a/runtime/src/runtime_api.rs +++ b/runtime/src/runtime_api.rs @@ -78,6 +78,8 @@ pub fn start_runtime_api() { crate::environment::get_data_directory, crate::environment::get_config_directory, crate::environment::read_user_language, + crate::environment::read_enterprise_env_config_id, + crate::environment::read_enterprise_env_config_server_url, crate::file_data::extract_data, crate::file_data::read_pdf, crate::log::get_log_paths,