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) server-digest SHA256) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 7E76C9633E for ; Tue, 24 Jan 2023 11:03:43 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 48ADE1C75 for ; Tue, 24 Jan 2023 11:03:42 +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, 24 Jan 2023 11:03:40 +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 7DD8245D8C for ; Tue, 24 Jan 2023 11:03:40 +0100 (CET) From: Lukas Wagner To: pbs-devel@lists.proxmox.com Date: Tue, 24 Jan 2023 11:03:35 +0100 Message-Id: <20230124100337.152394-5-l.wagner@proxmox.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20230124100337.152394-1-l.wagner@proxmox.com> References: <20230124100337.152394-1-l.wagner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 2.493 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 RCVD_IN_DNSWL_HI -5 Sender listed at https://www.dnswl.org/, high trust 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 v3 proxmox 4/6] ldap: 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, 24 Jan 2023 10:03:43 -0000 This commit adds the search_entities function, which allows to search for LDAP entities given certain provided criteria. Signed-off-by: Lukas Wagner --- proxmox-ldap/src/lib.rs | 89 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/proxmox-ldap/src/lib.rs b/proxmox-ldap/src/lib.rs index ac164db..903ce1a 100644 --- a/proxmox-ldap/src/lib.rs +++ b/proxmox-ldap/src/lib.rs @@ -1,4 +1,5 @@ use std::{ + collections::HashMap, fmt::{Display, Formatter}, fs, path::{Path, PathBuf}, @@ -6,6 +7,7 @@ use std::{ }; use anyhow::{bail, Error}; +use ldap3::adapters::{Adapter, EntriesOnly, PagedResults}; use ldap3::{Ldap, LdapConnAsync, LdapConnSettings, LdapResult, Scope, SearchEntry}; use native_tls::{Certificate, TlsConnector, TlsConnectorBuilder}; use serde::{Deserialize, Serialize}; @@ -49,6 +51,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 @@ -87,6 +109,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(|| { @@ -224,6 +291,28 @@ 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, "*")) + .collect()); + let user_classes = Or(parameters + .user_classes + .iter() + .map(|class| Condition("objectclass".into(), class)) + .collect()); + + if let Some(user_filter) = ¶meters.user_filter { + And(vec![Verbatim(user_filter), attr_wildcards, user_classes]) + } else { + And(vec![attr_wildcards, user_classes]) + } + .to_string() + } } #[allow(dead_code)] -- 2.30.2