From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 63F261FF17E for ; Tue, 16 Sep 2025 16:48:31 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 6E03717E07; Tue, 16 Sep 2025 16:48:38 +0200 (CEST) From: Shannon Sterz To: pdm-devel@lists.proxmox.com Date: Tue, 16 Sep 2025 16:48:21 +0200 Message-ID: <20250916144827.551806-6-s.sterz@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20250916144827.551806-1-s.sterz@proxmox.com> References: <20250916144827.551806-1-s.sterz@proxmox.com> MIME-Version: 1.0 X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1758034103884 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.049 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pdm-devel] [PATCH yew-comp 4/5] auth_edit_ldap: add helpers to properly edit ad & ldap realms X-BeenThere: pdm-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Datacenter Manager development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox Datacenter Manager development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pdm-devel-bounces@lists.proxmox.com Sender: "pdm-devel" previously this view would run into issues when editing more complex ldap and active directory realms. specifically: - when editing a pre-existing realm and not opening the second tab of the edit window, all attributes that would are configured through the second tab would be removed. values for those fields would also not be correctly loaded when opening that second tab. this issue stems from how `FormContext` works and it requires rendering all tabs before loading the form. fixed by specifying `force_render_all(true)` on the TabPanel in the form - properties specified via property strings (`sync-attributes` and `sync-default-options`) where not properly formatted when submitting them to the api, leading to errors warning about additional unknown parameters. fixed by parsing the form and properly formatting the two parameters. - properties specified via property strings would not be correctly loaded into the form as they weren't returned by the api in a way that is compatible with how EditWindow's loader works. fixed by implementing a loader that would properly parse these strings and adapting them to the expected format. - removing the last setting from the `sync-attributes` or `sync-defaults-options` property strings would not get properly removed as these two were missing from the appropriate `delete_empty_values()` call. Signed-off-by: Shannon Sterz --- src/auth_edit_ldap.rs | 108 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 105 insertions(+), 3 deletions(-) diff --git a/src/auth_edit_ldap.rs b/src/auth_edit_ldap.rs index 162f828..70fb638 100644 --- a/src/auth_edit_ldap.rs +++ b/src/auth_edit_ldap.rs @@ -1,9 +1,11 @@ use std::rc::Rc; use anyhow::Error; +use proxmox_client::ApiResponseData; use pwt::css::{Flex, Overflow}; use pwt::widget::form::{Checkbox, Combobox, FormContext, InputType, Number}; +use serde_json::Value; use yew::html::{IntoEventCallback, IntoPropValue}; use yew::virtual_dom::{VComp, VNode}; @@ -53,13 +55,109 @@ impl AuthEditLDAP { } } +async fn load_realm(url: impl Into) -> Result, Error> { + let mut response: ApiResponseData = crate::http_get_full(url, None).await?; + + response.data["anonymous_search"] = Value::Bool(!response.data["bind-dn"].is_string()); + + if let Value::String(sync_default_options) = response.data["sync-defaults-options"].take() { + let split = sync_default_options.split(","); + + for part in split { + let mut part = part.split("="); + + match part.next() { + Some("enable-new") => { + response.data["enable-new"] = Value::Bool(part.next() == Some("true")) + } + Some("remove-vanished") => { + if let Some(part) = part.next() { + for vanished_opt in part.split(";") { + response.data[&format!("remove-vanished-{vanished_opt}")] = + Value::Bool(true) + } + } + } + _ => {} + } + } + } + + if let Value::String(sync_attributes) = response.data["sync-attributes"].take() { + let split = sync_attributes.split(","); + + for opt in split { + let mut opt = opt.split("="); + if let (Some(name), Some(val)) = (opt.next(), opt.next()) { + response.data[name] = Value::String(val.to_string()); + } + } + } + + Ok(response) +} + +fn format_sync_and_default_options(data: &mut Value) -> Value { + let mut sync_default_options: Option = None; + + if let Value::Bool(val) = data["enable-new"].take() { + sync_default_options = Some(format!("enable-new={val}")) + } + + let mut remove_vanished: Vec<&str> = Vec::new(); + + for prop in ["acl", "entry", "properties"] { + let prop_name = format!("remove-vanished-{prop}"); + if data[&prop_name].take() == Value::Bool(true) { + remove_vanished.push(prop); + } + } + + if !remove_vanished.is_empty() { + let vanished = format!("remove-vanished={}", remove_vanished.join(";")); + + sync_default_options = sync_default_options + .map(|f| format!("{f},{vanished}")) + .or(Some(vanished)); + } + + if let Some(defaults) = sync_default_options { + data["sync-defaults-options"] = Value::String(defaults); + } + + let mut sync_attributes = Vec::new(); + + for attribute in ["firstname", "lastname", "email"] { + if let Value::String(val) = &data[attribute].take() { + sync_attributes.push(format!("{attribute}={val}")); + } + } + + if !sync_attributes.is_empty() { + data["sync-attributes"] = Value::String(sync_attributes.join(",")); + } + + let mut new = serde_json::json!({}); + + for (param, v) in data.as_object().unwrap().iter() { + if !v.is_null() { + new[param] = v.clone(); + } + } + + new +} + async fn create_item(form_ctx: FormContext, base_url: String) -> Result<(), Error> { - let data = form_ctx.get_submit_data(); + let mut data = form_ctx.get_submit_data(); + let data = format_sync_and_default_options(&mut data); crate::http_post(base_url, Some(data)).await } async fn update_item(form_ctx: FormContext, base_url: String) -> Result<(), Error> { - let data = form_ctx.get_submit_data(); + let mut data = form_ctx.get_submit_data(); + + let data = format_sync_and_default_options(&mut data); let data = delete_empty_values( &data, @@ -71,6 +169,8 @@ async fn update_item(form_ctx: FormContext, base_url: String) -> Result<(), Erro "comment", "user-classes", "filter", + "sync-attributes", + "sync-defaults-options", ], true, ); @@ -96,6 +196,7 @@ fn render_panel(form_ctx: FormContext, props: AuthEditLDAP) -> Html { TabBarItem::new().key("sync").label(tr!("Sync Options")), render_sync_form(form_ctx.clone(), props.clone()), ) + .force_render_all(true) .into() } @@ -301,7 +402,8 @@ impl Component for ProxmoxAuthEditLDAP { props .realm .as_ref() - .map(|realm| format!("{}/{}", props.base_url, percent_encode_component(realm))), + .map(|realm| format!("{}/{}", props.base_url, percent_encode_component(realm))) + .map(|url| move || load_realm(url.clone())), ) .renderer({ let props = props.clone(); -- 2.47.3 _______________________________________________ pdm-devel mailing list pdm-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel