From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 75C3F950C9 for ; Tue, 17 Jan 2023 15:20:57 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 5E65ECAEF for ; Tue, 17 Jan 2023 15:20:57 +0100 (CET) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS for ; Tue, 17 Jan 2023 15:20:56 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id 50B5C44F51 for ; Tue, 17 Jan 2023 15:20:56 +0100 (CET) From: Lukas Wagner To: pbs-devel@lists.proxmox.com Date: Tue, 17 Jan 2023 15:20:35 +0100 Message-Id: <20230117142037.847150-5-l.wagner@proxmox.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20230117142037.847150-1-l.wagner@proxmox.com> References: <20230117142037.847150-1-l.wagner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.154 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% 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 URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [lib.rs] Subject: [pbs-devel] [PATCH proxmox-ldap 4/6] allow searching for LDAP entities X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 17 Jan 2023 14:20:57 -0000 This commit adds the search_entities function, which allows to search for LDAP entities given certain provided criteria. Signed-off-by: Lukas Wagner --- src/lib.rs | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 40c4f6d..c80513e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,13 @@ use std::{ + collections::HashMap, fs, path::{Path, PathBuf}, time::Duration, }; use anyhow::{bail, Error}; -use ldap3::{ - Ldap, LdapConnAsync, LdapConnSettings, LdapResult, Scope, SearchEntry, -}; +use ldap3::{Ldap, LdapConnAsync, LdapConnSettings, LdapResult, Scope, SearchEntry}; +use ldap3::adapters::{Adapter, EntriesOnly, PagedResults}; use native_tls::{Certificate, TlsConnector, TlsConnectorBuilder}; use serde::{Deserialize, Serialize}; @@ -50,6 +50,26 @@ pub struct LdapConfig { pub certificate_store_path: Option, } +#[derive(Serialize, Deserialize)] +/// Parameters for LDAP user searches +pub struct SearchParameters { + /// Attributes that should be retrieved + pub attributes: Vec, + /// `objectclass`es of intereset + pub user_classes: Vec, + /// Custom user filter + pub user_filter: Option, +} + +#[derive(Serialize, Deserialize)] +/// Single LDAP user search result +pub struct SearchResult { + /// The full user's domain + pub dn: String, + /// Queried user attributes + pub attributes: HashMap>, +} + /// Connection to an LDAP server, can be used to authenticate users. pub struct LdapConnection { /// Configuration for this connection @@ -88,6 +108,51 @@ impl LdapConnection { Ok(()) } + /// Query entities matching given search parameters + pub async fn search_entities( + &self, + parameters: &SearchParameters, + ) -> Result, Error> { + let search_filter = Self::assemble_search_filter(parameters); + + let mut ldap = self.create_connection().await?; + + if let Some(bind_dn) = self.config.bind_dn.as_deref() { + let password = self.config.bind_password.as_deref().unwrap_or_default(); + let _: LdapResult = ldap.simple_bind(bind_dn, password).await?.success()?; + } + + let adapters: Vec>> = vec![ + Box::new(EntriesOnly::new()), + Box::new(PagedResults::new(500)), + ]; + let mut search = ldap + .streaming_search_with( + adapters, + &self.config.base_dn, + Scope::Subtree, + &search_filter, + parameters.attributes.clone(), + ) + .await?; + + let mut results = Vec::new(); + + while let Some(entry) = search.next().await? { + let entry = SearchEntry::construct(entry); + + results.push(SearchResult { + dn: entry.dn, + attributes: entry.attrs, + }) + } + let _res = search.finish().await.success()?; + + let _ = ldap.unbind().await; + + Ok(results) + } + /// Retrive port from LDAP configuration, otherwise use the correct default fn port_from_config(&self) -> u16 { self.config.port.unwrap_or_else(|| { @@ -225,6 +290,32 @@ impl LdapConnection { bail!("user not found") } + + fn assemble_search_filter(parameters: &SearchParameters) -> String { + use FilterElement::*; + + let attr_wildcards = Or(parameters + .attributes + .iter() + .map(|attr| Condition(attr.clone(), "*".into())) + .collect()); + let user_classes = Or(parameters + .user_classes + .iter() + .map(|class| Condition("objectclass".into(), class.clone())) + .collect()); + + if let Some(user_filter) = ¶meters.user_filter { + And(vec![ + Verbatim(user_filter.clone()), + attr_wildcards, + user_classes, + ]) + } else { + And(vec![attr_wildcards, user_classes]) + } + .to_string() + } } #[allow(dead_code)] -- 2.30.2