Skip to main content

simple_ldap/
lib.rs

1//! # simple-ldap
2//!
3//! This is a high-level LDAP client library created by wrapping the rust LDAP3 client.
4//! This provides high-level functions that helps to interact with LDAP.
5//!
6//! Wondering what this "LDAP" is anyway? Check this excellent [primer](https://github.com/inejge/ldap3/blob/27a247c8a6e4e2c86f664f4280c4c6499f0e9fe5/LDAP-primer.md) in the `ldap3` crate.
7//!
8//!
9//! ## Features
10//!
11//! - All the usual LDAP operations
12//! - Search result [deserialization](#deserialization)
13//! - Connection pooling
14//! - Streaming search with native rust [`Stream`](https://docs.rs/futures/latest/futures/stream/trait.Stream.html)s
15//! - Server Side Sort
16//!
17//!
18//! ## Usage
19//!
20//! Adding `simple_ldap` as a dependency to your project:
21//!
22//! ```commandline
23//! cargo add simple-ldap
24//! ```
25//!
26//! Most functionalities are defined on the [`LdapClient`] type. Have a look at the docs.
27//!
28//!
29//! ### Example
30//!
31//! Examples of individual operations are scattered throughout the docs, but here's the basic usage:
32//!
33//! ```no_run
34//! use simple_ldap::{
35//!     LdapClient, LdapConfig, SimpleDN,
36//!     filter::EqFilter,
37//!     ldap3::Scope
38//! };
39//! use url::Url;
40//! use serde::Deserialize;
41//!
42//! // A type for deserializing the search result into.
43//! #[derive(Debug, Deserialize)]
44//! struct User {
45//!     // // A convenience type for Distinguished Names.
46//!     pub dn: SimpleDN,
47//!     pub uid: String,
48//!     pub cn: String,
49//!     pub sn: String,
50//! }
51//!
52//!
53//! #[tokio::main]
54//! async fn main(){
55//!     let ldap_config = LdapConfig {
56//!         bind_dn: String::from("cn=manager"),
57//!         bind_password: String::from("password"),
58//!         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
59//!         dn_attribute: None,
60//!         connection_settings: None
61//!     };
62//!     let mut client = LdapClient::new(ldap_config).await.unwrap();
63//!     let name_filter = EqFilter::from("cn".to_string(), "Sam".to_string());
64//!     let user: User = client
65//!         .search(
66//!         "ou=people,dc=example,dc=com",
67//!         Scope::OneLevel,
68//!         &name_filter,
69//!         vec!["dn", "cn", "sn", "uid"],
70//!     ).await.unwrap();
71//! }
72//! ```
73//!
74//!
75//! ### Deserialization
76//!
77//! Search results are deserialized into user provided types using [`serde`](https://serde.rs/).
78//! Define a type that reflects the expected results of your search, and derive `Deserialize` for it. For example:
79//!
80//! ```
81//! use serde::Deserialize;
82//! use serde_with::serde_as;
83//! use serde_with::OneOrMany;
84//!
85//! use simple_ldap::SimpleDN;
86//!
87//! // A type for deserializing the search result into.
88//! #[serde_as] // serde_with for multiple values
89//! #[derive(Debug, Deserialize)]
90//! struct User {
91//!     // DN is always returned, whether you ask it or not.
92//!     // You could deserialize it as a plain String, but using
93//!     // SimpleDN gives you type-safety.
94//!     pub dn: SimpleDN,
95//!     pub cn: String,
96//!     // LDAP and Rust naming conventions differ.
97//!     // You can make up for the difference by using serde's renaming annotations.
98//!     #[serde(rename = "mayNotExist")]
99//!     pub may_not_exist: Option<String>,
100//!     #[serde_as(as = "OneOrMany<_>")] // serde_with for multiple values
101//!     pub multivalued_attribute: Vec<String>
102//! }
103//! ```
104//!
105//! Take care to actually request for all the attribute fields in the search.
106//! Otherwise they won't be returned, and the deserialization will fail (unless you used an `Option`).
107//!
108//!
109//! #### String attributes
110//!
111//! Most attributes are returned as strings. You can deserialize them into just Strings, but also into
112//! anything else that can supports deserialization from a string. E.g. perhaps the string represents a
113//! timestamp, and you can deserialize it directly into [`chrono::DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html).
114//!
115//!
116//! #### Binary attributes
117//!
118//! Some attributes may be binary encoded. (Active Directory especially has a bad habit of using these.)
119//! You can just capture the bytes directly into a `Vec<u8>`, but you can also use a type that knows how to
120//! deserialize from bytes. E.g. [`uuid::Uuid`](https://docs.rs/uuid/latest/uuid/struct.Uuid.html)
121//!
122//!
123//! #### Multi-valued attributes
124//!
125//! Multi-valued attributes should be marked as #[serde_as(as = "OneOrMany<_>")] using `serde_with`. Currently, there is a limitation when handing
126//! binary attributes. This will be fixed in the future. As a workaround, you can use `search_multi_valued` or `Record::to_multi_valued_record_`.
127//! To use those method all the attributes should be multi-valued.
128//!
129//!
130//! ## Compile time features
131//!
132//! * `tls-native` - (Enabled by default) Enables TLS support using the systems native implementation.
133//! * `tls-rustls` - Enables TLS support using `rustls`. **Conflicts with `tls-native` so you need to disable default features to use this.**
134//! * `pool` - Enable connection pooling
135//!
136
137use futures::{Stream, StreamExt, executor::block_on, stream};
138use ldap3::{
139    Ldap, LdapConnAsync, LdapConnSettings, LdapError, LdapResult, Mod, Scope, SearchEntry,
140    SearchStream, StreamState,
141    adapters::{Adapter, EntriesOnly, PagedResults},
142};
143use serde::{Deserialize, Serialize};
144use serde_value::Value;
145use std::{
146    collections::{HashMap, HashSet},
147    fmt, iter, num::NonZeroU16,
148};
149use thiserror::Error;
150use tracing::{Level, debug, error, info, instrument, warn};
151use url::Url;
152
153use filter::{AndFilter, EqFilter, Filter, OrFilter};
154use sort::adapter::ServerSideSort;
155
156pub mod filter;
157#[cfg(feature = "pool")]
158pub mod pool;
159pub mod simple_dn;
160mod sort;
161// Export the main type of the module right here in the root.
162pub use simple_dn::SimpleDN;
163// Used as an argument in the public API.
164pub use sort::adapter::SortBy;
165
166// Would likely be better if we could avoid re-exporting this.
167// I suspect it's only used in some configs?
168pub extern crate ldap3;
169
170const LDAP_ENTRY_DN: &str = "entryDN";
171const NO_SUCH_RECORD: u32 = 32;
172
173/// Configuration and authentication for LDAP connection
174#[derive(derive_more::Debug, Clone)]
175pub struct LdapConfig {
176    pub ldap_url: Url,
177    /// DistinguishedName, aka the "username" to use for the connection.
178    // Perhaps we don't want to use SimpleDN here, as it would make it impossible to bind to weird DNs.
179    pub bind_dn: String,
180    #[debug(skip)] // We don't want to print passwords.
181    pub bind_password: String,
182    pub dn_attribute: Option<String>,
183    /// Low level configuration for the connection.
184    /// You can probably skip it.
185    #[debug(skip)] // Debug omitted, because it just doesn't implement it.
186    pub connection_settings: Option<LdapConnSettings>,
187}
188
189///
190/// High-level LDAP client wrapper on top of ldap3 crate. This wrapper provides a high-level interface to perform LDAP operations
191/// including authentication, search, update, delete
192///
193#[derive(Debug, Clone)]
194pub struct LdapClient {
195    /// The internal connection handle.
196    ldap: Ldap,
197    dn_attr: Option<String>,
198}
199
200impl LdapClient {
201    ///
202    /// Creates a new asynchronous LDAP client.s
203    /// It's capable of running multiple operations concurrently.
204    ///
205    /// # Bind
206    ///
207    /// This performs a simple bind on the connection so need to worry about that.
208    ///
209    pub async fn new(config: LdapConfig) -> Result<Self, Error> {
210        debug!("Creating new connection");
211
212        // With or without connection settings
213        let (conn, mut ldap) = match config.connection_settings {
214            None => LdapConnAsync::from_url(&config.ldap_url).await,
215            Some(settings) => {
216                LdapConnAsync::from_url_with_settings(settings, &config.ldap_url).await
217            }
218        }
219        .map_err(|ldap_err| {
220            Error::Connection(
221                String::from("Failed to initialize LDAP connection."),
222                ldap_err,
223            )
224        })?;
225
226        ldap3::drive!(conn);
227
228        ldap.simple_bind(&config.bind_dn, &config.bind_password)
229            .await
230            .map_err(|ldap_err| Error::Connection(String::from("Bind failed"), ldap_err))?
231            .success()
232            .map_err(|ldap_err| Error::Connection(String::from("Bind failed"), ldap_err))?;
233
234        Ok(Self {
235            dn_attr: config.dn_attribute,
236            ldap,
237        })
238    }
239}
240
241impl LdapClient {
242    /// Returns the ldap3 client
243    #[deprecated = "This abstraction leakage will be removed in a future release.
244                    Use the provided methods instead. If something's missing, open an issue in github."]
245    pub fn get_inner(&self) -> Ldap {
246        self.ldap.clone()
247    }
248
249    /// End the LDAP connection.
250    ///
251    /// **Caution advised!**
252    ///
253    /// This will close the connection for all clones of this client as well,
254    /// including open streams. So make sure that you're really good to close.
255    ///
256    /// Closing an LDAP connection with an unbind is *a curtesy.*
257    /// It's fine to skip it, and because of the async hurdles outlined above,
258    /// I would perhaps even recommend it.
259    // Consuming self to prevent accidental use after unbind.
260    // This also conveniently prevents calling this with pooled clients, as the
261    // wrapper `Object` prohibits moving.
262    pub async fn unbind(mut self) -> Result<(), Error> {
263        match self.ldap.unbind().await {
264            Ok(_) => Ok(()),
265            Err(error) => Err(Error::Close(String::from("Failed to unbind"), error)),
266        }
267    }
268
269    ///
270    /// The user is authenticated by searching for the user in the LDAP server.
271    /// The search is performed using the provided filter. The filter should be a filter that matches a single user.
272    ///
273    /// # Arguments
274    ///
275    /// * `base` - The base DN to search for the user
276    /// * `uid` - The uid of the user
277    /// * `password` - The password of the user
278    /// * `filter` - The filter to search for the user
279    ///
280    ///
281    /// # Returns
282    ///
283    /// * `Result<(), Error>` - Returns an error if the authentication fails
284    ///
285    ///
286    /// # Example
287    ///
288    /// ```no_run
289    /// use simple_ldap::{
290    ///     LdapClient, LdapConfig,
291    ///     filter::EqFilter
292    /// };
293    /// use url::Url;
294    ///
295    /// #[tokio::main]
296    /// async fn main(){
297    ///     let ldap_config = LdapConfig {
298    ///         bind_dn: String::from("cn=manager"),
299    ///         bind_password: String::from("password"),
300    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
301    ///         dn_attribute: None,
302    ///         connection_settings: None
303    ///     };
304    ///
305    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
306    ///     let name_filter = EqFilter::from("cn".to_string(), "Sam".to_string());
307    ///
308    ///     let result = client.authenticate("", "Sam", "password", Box::new(name_filter)).await;
309    /// }
310    /// ```
311    pub async fn authenticate(
312        &mut self,
313        base: &str,
314        uid: &str,
315        password: &str,
316        filter: Box<dyn Filter>,
317    ) -> Result<(), Error> {
318        let attr_dn = self.dn_attr.as_deref().unwrap_or(LDAP_ENTRY_DN);
319
320        let rs = self
321            .ldap
322            .search(base, Scope::OneLevel, filter.filter().as_str(), [attr_dn])
323            .await
324            .map_err(|e| Error::Query("Unable to query user for authentication".into(), e))?;
325
326        let (data, _rs) = rs
327            .success()
328            .map_err(|e| Error::Query("Could not find user for authentication".into(), e))?;
329
330        if data.is_empty() {
331            return Err(Error::NotFound(format!("No record found {uid:?}")));
332        }
333        if data.len() > 1 {
334            return Err(Error::MultipleResults(format!(
335                "Found multiple records for uid {uid:?}"
336            )));
337        }
338
339        let record = data.first().unwrap().to_owned();
340        let record = SearchEntry::construct(record);
341        let result: HashMap<&str, String> = record
342            .attrs
343            .iter()
344            .filter(|(_, value)| !value.is_empty())
345            .map(|(arrta, value)| (arrta.as_str(), value.first().unwrap().clone()))
346            .collect();
347
348        let entry_dn = result.get(attr_dn).ok_or_else(|| {
349            Error::AuthenticationFailed(format!("Unable to retrieve DN of user {uid}"))
350        })?;
351
352        self.ldap
353            .simple_bind(entry_dn, password)
354            .await
355            .map_err(|_| Error::AuthenticationFailed(format!("Error authenticating user: {uid:?}")))
356            .and_then(|r| {
357                r.success().map_err(|_| {
358                    Error::AuthenticationFailed(format!("Error authenticating user: {uid:?}"))
359                })
360            })
361            .and(Ok(()))
362    }
363
364    async fn search_inner<'a, F, A, S>(
365        &mut self,
366        base: &str,
367        scope: Scope,
368        filter: &F,
369        attributes: A,
370    ) -> Result<SearchEntry, Error>
371    where
372        F: Filter,
373        A: AsRef<[S]> + Send + Sync + 'a,
374        S: AsRef<str> + Send + Sync + 'a,
375    {
376        let search = self
377            .ldap
378            .search(base, scope, filter.filter().as_str(), attributes)
379            .await;
380        if let Err(error) = search {
381            return Err(Error::Query(
382                format!("Error searching for record: {error:?}"),
383                error,
384            ));
385        }
386        let result = search.unwrap().success();
387        if let Err(error) = result {
388            return Err(Error::Query(
389                format!("Error searching for record: {error:?}"),
390                error,
391            ));
392        }
393
394        let records = result.unwrap().0;
395
396        if records.len() > 1 {
397            return Err(Error::MultipleResults(String::from(
398                "Found multiple records for the search criteria",
399            )));
400        }
401
402        if records.is_empty() {
403            return Err(Error::NotFound(String::from(
404                "No records found for the search criteria",
405            )));
406        }
407
408        let record = records.first().unwrap();
409
410        Ok(SearchEntry::construct(record.to_owned()))
411    }
412
413    ///
414    /// Search a single value from the LDAP server. The search is performed using the provided filter.
415    /// The filter should be a filter that matches a single record. if the filter matches multiple users, an error is returned.
416    /// This operation will treat all the attributes as single-valued, silently ignoring the possible extra
417    /// values.
418    ///
419    ///
420    /// # Arguments
421    ///
422    /// * `base` - The base DN to search for the user
423    /// * `scope` - The scope of the search
424    /// * `filter` - The filter to search for the user
425    /// * `attributes` - The attributes to return from the search
426    ///
427    ///
428    /// # Returns
429    ///
430    /// * `Result<T, Error>` - The result will be mapped to a struct of type T
431    ///
432    ///
433    /// # Example
434    ///
435    /// ```no_run
436    /// use simple_ldap::{
437    ///     LdapClient, LdapConfig,
438    ///     filter::EqFilter,
439    ///     ldap3::Scope
440    /// };
441    /// use url::Url;
442    /// use serde::Deserialize;
443    ///
444    ///
445    /// #[derive(Debug, Deserialize)]
446    /// struct User {
447    ///     uid: String,
448    ///     cn: String,
449    ///     sn: String,
450    /// }
451    ///
452    /// #[tokio::main]
453    /// async fn main(){
454    ///     let ldap_config = LdapConfig {
455    ///         bind_dn: String::from("cn=manager"),
456    ///         bind_password: String::from("password"),
457    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
458    ///         dn_attribute: None,
459    ///         connection_settings: None
460    ///     };
461    ///
462    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
463    ///
464    ///     let name_filter = EqFilter::from("cn".to_string(), "Sam".to_string());
465    ///     let user_result: User = client
466    ///         .search(
467    ///         "ou=people,dc=example,dc=com",
468    ///         Scope::OneLevel,
469    ///         &name_filter,
470    ///         vec!["cn", "sn", "uid"],
471    ///     ).await
472    ///     .unwrap();
473    /// }
474    /// ```
475    ///
476    pub async fn search<'a, F, A, S, T>(
477        &mut self,
478        base: &str,
479        scope: Scope,
480        filter: &F,
481        attributes: A,
482    ) -> Result<T, Error>
483    where
484        F: Filter,
485        A: AsRef<[S]> + Send + Sync + 'a,
486        S: AsRef<str> + Send + Sync + 'a,
487        T: for<'de> serde::Deserialize<'de>,
488    {
489        let search_entry = self.search_inner(base, scope, filter, attributes).await?;
490        to_value(search_entry)
491    }
492
493    ///
494    /// Search a single value from the LDAP server. The search is performed using the provided filter.
495    /// The filter should be a filter that matches a single record. if the filter matches multiple users, an error is returned.
496    /// This operation is useful when records has multi-valued attributes.
497    ///
498    ///
499    /// # Arguments
500    ///
501    /// * `base` - The base DN to search for the user
502    /// * `scope` - The scope of the search
503    /// * `filter` - The filter to search for the user
504    /// * `attributes` - The attributes to return from the search
505    ///
506    ///
507    /// # Returns
508    ///
509    /// * `Result<T, Error>` - The result will be mapped to a struct of type T
510    ///
511    ///
512    /// # Example
513    ///
514    /// ```no_run
515    /// use simple_ldap::{
516    ///     LdapClient, LdapConfig,
517    ///     filter::EqFilter,
518    ///     ldap3::Scope
519    /// };
520    /// use url::Url;
521    /// use serde::Deserialize;
522    ///
523    ///
524    /// #[derive(Debug, Deserialize)]
525    /// struct TestMultiValued {
526    ///    key1: Vec<String>,
527    ///    key2: Vec<String>,
528    /// }
529    ///
530    /// #[tokio::main]
531    /// async fn main(){
532    ///     let ldap_config = LdapConfig {
533    ///         bind_dn: String::from("cn=manager"),
534    ///         bind_password: String::from("password"),
535    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
536    ///         dn_attribute: None,
537    ///         connection_settings: None
538    ///     };
539    ///
540    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
541    ///
542    ///     let name_filter = EqFilter::from("cn".to_string(), "Sam".to_string());
543    ///     let user_result = client.search_multi_valued::<TestMultiValued>(
544    ///         "",
545    ///         Scope::OneLevel,
546    ///         &name_filter,
547    ///         &vec!["cn", "sn", "uid"]
548    ///     ).await;
549    /// }
550    /// ```
551    ///
552    pub async fn search_multi_valued<T: for<'a> serde::Deserialize<'a>>(
553        &mut self,
554        base: &str,
555        scope: Scope,
556        filter: &impl Filter,
557        attributes: &Vec<&str>,
558    ) -> Result<T, Error> {
559        let search_entry = self.search_inner(base, scope, filter, attributes).await?;
560        to_multi_value(search_entry)
561    }
562
563    ///
564    /// This method is used to search multiple records from the LDAP server. The search is performed using the provided filter.
565    /// Method will return a Stream. The stream will lazily fetch the results, resulting in a smaller
566    /// memory footprint.
567    ///
568    /// This is the recommended search method, especially if you don't know that the result set is going to be small.
569    ///
570    ///
571    /// # Arguments
572    ///
573    /// * `base` - The base DN to search for the user
574    /// * `scope` - The scope of the search
575    /// * `filter` - The filter to search for the user
576    /// * `attributes` - The attributes to return from the search
577    /// * `page_size` - Fetch the results in pages. Recommended for large result sets.
578    ///   Uses the Simple Paged Results LDAP extension.
579    /// * `sort_by` - Sort the results using Server Side Sort LDAP extension.
580    ///
581    ///
582    /// # Returns
583    //
584    /// A stream that can be used to iterate through the search results.
585    ///
586    ///
587    /// ## Blocking drop caveat
588    ///
589    /// Dropping this stream may issue blocking network requests to cancel the search.
590    /// Running the stream to it's end will minimize the chances of this happening.
591    /// You should take this into account if latency is critical to your application.
592    ///
593    /// We're waiting for [`AsyncDrop`](https://github.com/rust-lang/rust/issues/126482) for implementing this properly.
594    ///
595    ///
596    ///
597    /// # Example
598    ///
599    /// ```no_run
600    /// use simple_ldap::{
601    ///     LdapClient, LdapConfig, SortBy,
602    ///     filter::EqFilter,
603    ///     ldap3::Scope,
604    /// };
605    /// use url::Url;
606    /// use serde::Deserialize;
607    /// use futures::{StreamExt, TryStreamExt};
608    /// use std::num::NonZero;
609    ///
610    ///
611    /// #[derive(Deserialize, Debug)]
612    /// struct User {
613    ///     uid: String,
614    ///     cn: String,
615    ///     sn: String,
616    /// }
617    ///
618    /// #[tokio::main]
619    /// async fn main(){
620    ///     let ldap_config = LdapConfig {
621    ///         bind_dn: String::from("cn=manager"),
622    ///         bind_password: String::from("password"),
623    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
624    ///         dn_attribute: None,
625    ///         connection_settings: None
626    ///     };
627    ///
628    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
629    ///
630    ///     let name_filter = EqFilter::from(String::from("cn"), String::from("Sam"));
631    ///     let attributes = vec!["cn", "sn", "uid"];
632    ///     let sort = vec![
633    ///         SortBy {
634    ///             attribute: String::from("sn"),
635    ///             reverse: true
636    ///         }
637    ///     ];
638    ///
639    ///     let stream = client.streaming_search(
640    ///         "ou=people,dc=example,dc=com",
641    ///         Scope::OneLevel,
642    ///         &name_filter,
643    ///         attributes,
644    ///         Some(NonZero::new(200).unwrap()), // The pagesize
645    ///         sort
646    ///     ).await.unwrap();
647    ///
648    ///     // Map the search results to User type.
649    ///     stream.and_then(async |record| record.to_record())
650    ///          // Do something with the Users concurrently.
651    ///         .try_for_each(async |user: User| {
652    ///             println!("User: {:?}", user);
653    ///             Ok(())
654    ///         })
655    ///         .await
656    ///         .unwrap();
657    /// }
658    /// ```
659    ///
660    pub async fn streaming_search<'a, F, A, S>(
661        // This self reference  lifetime has some nuance behind it.
662        //
663        // In principle it could just be a value, but then you wouldn't be able to call this
664        // with a pooled client, as the deadpool `Object` wrapper only ever gives out references.
665        //
666        // The lifetime is needed to guarantee that the client is not returned to the pool before
667        // the returned stream is finished. This requirement is artificial. Internally the `ldap3` client
668        // just makes copy. So this lifetime is here just to enforce correct pool usage.
669        &'a mut self,
670        base: &str,
671        scope: Scope,
672        filter: &F,
673        attributes: A,
674        // The internal adapter takes i32, but half of its range is invalid.
675        page_size: Option<NonZeroU16>,
676        sort_by: Vec<SortBy>,
677    ) -> Result<impl Stream<Item = Result<Record, Error>> + use<'a, F, A, S>, Error>
678    where
679        F: Filter,
680        // PagedResults requires Clone and Debug too.
681        A: AsRef<[S]> + Send + Sync + Clone + fmt::Debug + 'a,
682        S: AsRef<str> + Send + Sync + Clone + fmt::Debug + 'a,
683    {
684        // Define the needed adapters.
685
686        // Entries only is only needed with paging.
687        let (paging_adapter, entries_only_adapter) = page_size.map(|non_zero|
688                (PagedResults::new(non_zero.get().into()), EntriesOnly::new())
689            )
690            .map(|(page_adapter, entries_adapter)| (Box::new(page_adapter) as _, Box::new(entries_adapter) as _))
691            .unzip();
692
693        // Empty vec just means that we won't use the search adapter.
694        let sort_adapter: Option<Box<dyn Adapter<'a, S, A>>> = vec_to_option(sort_by)
695            .map(ServerSideSort::new)
696            .transpose()
697            .map_err(|duplicate_args_err| Error::Sort(duplicate_args_err.to_string()))?
698            .map(|adapter| Box::new(adapter) as _);
699
700        let maybe_adapters: Vec<Option<Box<dyn Adapter<'a, S, A>>>> = vec![
701            // Sort needs to be before paging, so that it's control will be included in all the page requests.
702            sort_adapter,
703            entries_only_adapter,
704            paging_adapter,
705        ];
706
707        // This might end up as no adapters but that's perfectly fine too.
708        // Internally the non adapted streaming search would anyway just call the same thing with an empty adapter list.
709        let adapters: Vec<_> = maybe_adapters.into_iter().flatten().collect();
710
711        let search_stream = self
712            .ldap
713            .streaming_search_with(adapters, base, scope, filter.filter().as_str(), attributes)
714            .await
715            .map_err(|ldap_error| {
716                Error::Query(
717                    format!("Error searching for record: {ldap_error:?}"),
718                    ldap_error,
719                )
720            })?;
721
722        to_native_stream(search_stream)
723    }
724
725    ///
726    /// Create a new record in the LDAP server. The record will be created in the provided base DN.
727    ///
728    /// # Arguments
729    ///
730    /// * `uid` - The uid of the record
731    /// * `base` - The base DN to create the record
732    /// * `data` - The attributes of the record
733    ///
734    ///
735    /// # Returns
736    ///
737    /// * `Result<(), Error>` - Returns an error if the record creation fails
738    ///
739    ///
740    /// # Example
741    ///
742    /// ```no_run
743    /// use simple_ldap::{LdapClient, LdapConfig};
744    /// use url::Url;
745    /// use std::collections::HashSet;
746    ///
747    /// #[tokio::main]
748    /// async fn main(){
749    ///     let ldap_config = LdapConfig {
750    ///         bind_dn: String::from("cn=manager"),
751    ///         bind_password: String::from("password"),
752    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
753    ///         dn_attribute: None,
754    ///         connection_settings: None
755    ///     };
756    ///
757    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
758    ///
759    ///     let data = vec![
760    ///         ( "objectClass",HashSet::from(["organizationalPerson", "inetorgperson", "top", "person"]),),
761    ///         ("uid",HashSet::from(["bd9b91ec-7a69-4166-bf67-cc7e553b2fd9"]),),
762    ///         ("cn", HashSet::from(["Kasun"])),
763    ///         ("sn", HashSet::from(["Ranasingh"])),
764    ///     ];
765    ///
766    ///     let result = client.create("bd9b91ec-7a69-4166-bf67-cc7e553b2fd9", "ou=people,dc=example,dc=com", data).await;
767    /// }
768    /// ```
769    ///
770    pub async fn create(
771        &mut self,
772        uid: &str,
773        base: &str,
774        data: Vec<(&str, HashSet<&str>)>,
775    ) -> Result<(), Error> {
776        let dn = format!("uid={uid},{base}");
777        let save = self.ldap.add(dn.as_str(), data).await;
778        if let Err(err) = save {
779            return Err(Error::Create(format!("Error saving record: {err:?}"), err));
780        }
781        let save = save.unwrap().success();
782
783        if let Err(err) = save {
784            return Err(Error::Create(format!("Error saving record: {err:?}"), err));
785        }
786        let res = save.unwrap();
787        debug!("Successfully created record result: {:?}", res);
788        Ok(())
789    }
790
791    ///
792    /// Update a record in the LDAP server. The record will be updated in the provided base DN.
793    ///
794    /// # Arguments
795    ///
796    /// * `uid` - The uid of the record
797    /// * `base` - The base DN to update the record
798    /// * `data` - The attributes of the record
799    /// * `new_uid` - The new uid of the record. If the new uid is provided, the uid of the record will be updated.
800    ///
801    ///
802    /// # Returns
803    ///
804    /// * `Result<(), Error>` - Returns an error if the record update fails
805    ///
806    ///
807    /// # Example
808    ///
809    /// ```no_run
810    /// use simple_ldap::{
811    ///     LdapClient, LdapConfig,
812    ///     ldap3::Mod
813    /// };
814    /// use url::Url;
815    /// use std::collections::HashSet;
816    ///
817    /// #[tokio::main]
818    /// async fn main(){
819    ///     let ldap_config = LdapConfig {
820    ///         bind_dn: String::from("cn=manager"),
821    ///         bind_password: String::from("password"),
822    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
823    ///         dn_attribute: None,
824    ///         connection_settings: None
825    ///     };
826    ///
827    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
828    ///
829    ///     let data = vec![
830    ///         Mod::Replace("cn", HashSet::from(["Jhon_Update"])),
831    ///         Mod::Replace("sn", HashSet::from(["Eliet_Update"])),
832    ///     ];
833    ///
834    ///     let result = client.update(
835    ///         "e219fbc0-6df5-4bc3-a6ee-986843bb157e",
836    ///         "ou=people,dc=example,dc=com",
837    ///         data,
838    ///         None
839    ///     ).await;
840    /// }
841    /// ```
842    ///
843    pub async fn update(
844        &mut self,
845        uid: &str,
846        base: &str,
847        data: Vec<Mod<&str>>,
848        new_uid: Option<&str>,
849    ) -> Result<(), Error> {
850        let dn = format!("uid={uid},{base}");
851
852        let res = self.ldap.modify(dn.as_str(), data).await;
853        if let Err(err) = res {
854            return Err(Error::Update(
855                format!("Error updating record: {err:?}"),
856                err,
857            ));
858        }
859
860        let res = res.unwrap().success();
861        if let Err(err) = res {
862            match err {
863                LdapError::LdapResult { result } => {
864                    if result.rc == NO_SUCH_RECORD {
865                        return Err(Error::NotFound(format!(
866                            "No records found for the uid: {uid:?}"
867                        )));
868                    }
869                }
870                _ => {
871                    return Err(Error::Update(
872                        format!("Error updating record: {err:?}"),
873                        err,
874                    ));
875                }
876            }
877        }
878
879        if new_uid.is_none() {
880            return Ok(());
881        }
882
883        let new_uid = new_uid.unwrap();
884        if !uid.eq_ignore_ascii_case(new_uid) {
885            let new_dn = format!("uid={new_uid}");
886            let dn_update = self
887                .ldap
888                .modifydn(dn.as_str(), new_dn.as_str(), true, None)
889                .await;
890            if let Err(err) = dn_update {
891                error!("Failed to update dn for record {:?} error {:?}", uid, err);
892                return Err(Error::Update(
893                    format!("Failed to update dn for record {uid:?}"),
894                    err,
895                ));
896            }
897
898            let dn_update = dn_update.unwrap().success();
899            if let Err(err) = dn_update {
900                error!("Failed to update dn for record {:?} error {:?}", uid, err);
901                return Err(Error::Update(
902                    format!("Failed to update dn for record {uid:?}"),
903                    err,
904                ));
905            }
906
907            let res = dn_update.unwrap();
908            debug!("Successfully updated dn result: {:?}", res);
909        }
910
911        Ok(())
912    }
913
914    ///
915    /// Delete a record in the LDAP server. The record will be deleted in the provided base DN.
916    ///
917    /// # Arguments
918    ///
919    /// * `uid` - The uid of the record
920    /// * `base` - The base DN to delete the record
921    ///
922    ///
923    /// # Returns
924    ///
925    /// * `Result<(), Error>` - Returns an error if the record delete fails
926    ///
927    ///
928    /// # Example
929    ///
930    /// ```no_run
931    /// use simple_ldap::{LdapClient, LdapConfig};
932    /// use url::Url;
933    ///
934    /// #[tokio::main]
935    /// async fn main(){
936    ///     let ldap_config = LdapConfig {
937    ///         bind_dn: String::from("cn=manager"),
938    ///         bind_password: String::from("password"),
939    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
940    ///         dn_attribute: None,
941    ///         connection_settings: None
942    ///     };
943    ///
944    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
945    ///
946    ///     let result = client.delete("e219fbc0-6df5-4bc3-a6ee-986843bb157e", "ou=people,dc=example,dc=com").await;
947    /// }
948    /// ```
949    pub async fn delete(&mut self, uid: &str, base: &str) -> Result<(), Error> {
950        let dn = format!("uid={uid},{base}");
951        let delete = self.ldap.delete(dn.as_str()).await;
952
953        if let Err(err) = delete {
954            return Err(Error::Delete(
955                format!("Error deleting record: {err:?}"),
956                err,
957            ));
958        }
959        let delete = delete.unwrap().success();
960        if let Err(err) = delete {
961            match err {
962                LdapError::LdapResult { result } => {
963                    if result.rc == NO_SUCH_RECORD {
964                        return Err(Error::NotFound(format!(
965                            "No records found for the uid: {uid:?}"
966                        )));
967                    }
968                }
969                _ => {
970                    return Err(Error::Delete(
971                        format!("Error deleting record: {err:?}"),
972                        err,
973                    ));
974                }
975            }
976        }
977        debug!("Successfully deleted record result: {:?}", uid);
978        Ok(())
979    }
980
981    ///
982    /// Create a new group in the LDAP server. The group will be created in the provided base DN.
983    ///
984    /// # Arguments
985    ///
986    /// * `group_name` - The name of the group
987    /// * `group_ou` - The ou of the group
988    /// * `description` - The description of the group
989    ///
990    /// # Returns
991    ///
992    /// * `Result<(), Error>` - Returns an error if the group creation fails
993    ///
994    ///
995    /// # Example
996    ///
997    /// ```no_run
998    /// use simple_ldap::{LdapClient, LdapConfig};
999    /// use url::Url;
1000    ///
1001    /// #[tokio::main]
1002    /// async fn main(){
1003    ///     let ldap_config = LdapConfig {
1004    ///         bind_dn: String::from("cn=manager"),
1005    ///         bind_password: String::from("password"),
1006    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1007    ///         dn_attribute: None,
1008    ///         connection_settings: None
1009    ///     };
1010    ///
1011    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
1012    ///
1013    ///     let result = client.create_group("test_group", "ou=groups,dc=example,dc=com", "test group").await;
1014    /// }
1015    /// ```
1016    pub async fn create_group(
1017        &mut self,
1018        group_name: &str,
1019        group_ou: &str,
1020        description: &str,
1021    ) -> Result<(), Error> {
1022        let dn = format!("cn={group_name},{group_ou}");
1023
1024        let data = vec![
1025            ("objectClass", HashSet::from(["top", "groupOfNames"])),
1026            ("cn", HashSet::from([group_name])),
1027            ("ou", HashSet::from([group_ou])),
1028            ("description", HashSet::from([description])),
1029        ];
1030        let save = self.ldap.add(dn.as_str(), data).await;
1031        if let Err(err) = save {
1032            return Err(Error::Create(format!("Error saving record: {err:?}"), err));
1033        }
1034        let save = save.unwrap().success();
1035
1036        if let Err(err) = save {
1037            return Err(Error::Create(format!("Error creating group: {err:?}"), err));
1038        }
1039        let res = save.unwrap();
1040        debug!("Successfully created group result: {:?}", res);
1041        Ok(())
1042    }
1043
1044    ///
1045    /// Add users to a group in the LDAP server. The group will be updated in the provided base DN.
1046    ///
1047    /// # Arguments
1048    ///
1049    /// * `users` - The list of users to add to the group
1050    /// * `group_dn` - The dn of the group
1051    ///
1052    ///
1053    /// # Returns
1054    ///
1055    /// * `Result<(), Error>` - Returns an error if failed to add users to the group
1056    ///
1057    ///
1058    /// # Example
1059    ///
1060    /// ```no_run
1061    /// use simple_ldap::{LdapClient, LdapConfig};
1062    /// use url::Url;
1063    ///
1064    /// #[tokio::main]
1065    /// async fn main(){
1066    ///     let ldap_config = LdapConfig {
1067    ///         bind_dn: String::from("cn=manager"),
1068    ///         bind_password: String::from("password"),
1069    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1070    ///         dn_attribute: None,
1071    ///         connection_settings: None
1072    ///     };
1073    ///
1074    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
1075    ///
1076    ///     let result = client.add_users_to_group(
1077    ///         vec!["uid=bd9b91ec-7a69-4166-bf67-cc7e553b2fd9,ou=people,dc=example,dc=com"],
1078    ///         "cn=test_group,ou=groups,dc=example,dc=com").await;
1079    /// }
1080    /// ```
1081    pub async fn add_users_to_group(
1082        &mut self,
1083        users: Vec<&str>,
1084        group_dn: &str,
1085    ) -> Result<(), Error> {
1086        let mut mods = Vec::new();
1087        let users = users.iter().copied().collect::<HashSet<&str>>();
1088        mods.push(Mod::Replace("member", users));
1089        let res = self.ldap.modify(group_dn, mods).await;
1090        if let Err(err) = res {
1091            return Err(Error::Update(
1092                format!("Error updating record: {err:?}"),
1093                err,
1094            ));
1095        }
1096
1097        let res = res.unwrap().success();
1098        if let Err(err) = res {
1099            match err {
1100                LdapError::LdapResult { result } => {
1101                    if result.rc == NO_SUCH_RECORD {
1102                        return Err(Error::NotFound(format!(
1103                            "No records found for the uid: {group_dn:?}"
1104                        )));
1105                    }
1106                }
1107                _ => {
1108                    return Err(Error::Update(
1109                        format!("Error updating record: {err:?}"),
1110                        err,
1111                    ));
1112                }
1113            }
1114        }
1115        Ok(())
1116    }
1117
1118    ///
1119    /// Get users of a group in the LDAP server. The group will be searched in the provided base DN.
1120    ///
1121    /// # Arguments
1122    ///
1123    /// * `group_dn` - The dn of the group
1124    /// * `base_dn` - The base dn to search for the users
1125    /// * `scope` - The scope of the search
1126    /// * `attributes` - The attributes to return from the search
1127    ///
1128    ///
1129    /// # Returns
1130    ///
1131    /// * `Result<Vec<T>, Error>` - Returns a vector of structs of type T
1132    ///
1133    ///
1134    /// # Example
1135    ///
1136    /// ```no_run
1137    /// use simple_ldap::{
1138    ///     LdapClient, LdapConfig,
1139    ///     ldap3::Scope
1140    /// };
1141    /// use url::Url;
1142    /// use serde::Deserialize;
1143    ///
1144    /// #[derive(Debug, Deserialize)]
1145    /// struct User {
1146    ///     uid: String,
1147    ///     cn: String,
1148    ///     sn: String,
1149    /// }
1150    ///
1151    /// #[tokio::main]
1152    /// async fn main(){
1153    ///     let ldap_config = LdapConfig {
1154    ///         bind_dn: String::from("cn=manager"),
1155    ///         bind_password: String::from("password"),
1156    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1157    ///         dn_attribute: None,
1158    ///         connection_settings: None
1159    ///     };
1160    ///
1161    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
1162    ///
1163    ///     let members: Vec<User> = client.get_members(
1164    ///         "cn=test_group,ou=groups,dc=example,dc=com",
1165    ///         "ou=people,dc=example,dc=com",
1166    ///         Scope::OneLevel,
1167    ///         vec!["cn", "sn", "uid"]
1168    ///     ).await
1169    ///     .unwrap();
1170    /// }
1171    /// ```
1172    ///
1173    pub async fn get_members<'a, A, S, T>(
1174        &mut self,
1175        group_dn: &str,
1176        base_dn: &str,
1177        scope: Scope,
1178        attributes: A,
1179    ) -> Result<Vec<T>, Error>
1180    where
1181        A: AsRef<[S]> + Send + Sync + Clone + fmt::Debug + 'a,
1182        S: AsRef<str> + Send + Sync + Clone + fmt::Debug + 'a,
1183        T: for<'de> serde::Deserialize<'de>,
1184    {
1185        let search = self
1186            .ldap
1187            .search(
1188                group_dn,
1189                Scope::Base,
1190                "(objectClass=groupOfNames)",
1191                vec!["member"],
1192            )
1193            .await;
1194
1195        if let Err(error) = search {
1196            return Err(Error::Query(
1197                format!("Error searching for record: {error:?}"),
1198                error,
1199            ));
1200        }
1201        let result = search.unwrap().success();
1202        if let Err(error) = result {
1203            return Err(Error::Query(
1204                format!("Error searching for record: {error:?}"),
1205                error,
1206            ));
1207        }
1208
1209        let records = result.unwrap().0;
1210
1211        if records.len() > 1 {
1212            return Err(Error::MultipleResults(String::from(
1213                "Found multiple records for the search criteria",
1214            )));
1215        }
1216
1217        if records.is_empty() {
1218            return Err(Error::NotFound(String::from(
1219                "No records found for the search criteria",
1220            )));
1221        }
1222
1223        let record = records.first().unwrap();
1224
1225        let mut or_filter = OrFilter::default();
1226
1227        let search_entry = SearchEntry::construct(record.to_owned());
1228        search_entry
1229            .attrs
1230            .into_iter()
1231            .filter(|(_, value)| !value.is_empty())
1232            .map(|(arrta, value)| (arrta.to_owned(), value.to_owned()))
1233            .filter(|(attra, _)| attra.eq("member"))
1234            .flat_map(|(_, value)| value)
1235            .map(|val| {
1236                val.split(',').collect::<Vec<&str>>()[0]
1237                    .split('=')
1238                    .map(|split| split.to_string())
1239                    .collect::<Vec<String>>()
1240            })
1241            .map(|uid| EqFilter::from(uid[0].to_string(), uid[1].to_string()))
1242            .for_each(|eq| or_filter.add(Box::new(eq)));
1243
1244        let result = self
1245            .streaming_search(base_dn, scope, &or_filter, attributes, None, Vec::new())
1246            .await;
1247
1248        let mut members = Vec::new();
1249        match result {
1250            Ok(result) => {
1251                let mut stream = Box::pin(result);
1252                while let Some(member) = stream.next().await {
1253                    match member {
1254                        Ok(member) => {
1255                            let user: T = member.to_record().unwrap();
1256                            members.push(user);
1257                        }
1258                        Err(err) => {
1259                            // TODO: Exit with an error instead?
1260                            error!("Error getting member error {:?}", err);
1261                        }
1262                    }
1263                }
1264                return Ok(members);
1265            }
1266            Err(err) => {
1267                // TODO: Exit with an error instead?
1268                error!("Error getting members {:?} error {:?}", group_dn, err);
1269            }
1270        }
1271
1272        Ok(members)
1273    }
1274
1275    ///
1276    /// Remove users from a group in the LDAP server. The group will be updated in the provided base DN.
1277    /// This method will remove all the users provided from the group.
1278    ///
1279    ///
1280    /// # Arguments
1281    ///
1282    /// * `group_dn` - The dn of the group
1283    /// * `users` - The list of users to remove from the group
1284    ///
1285    ///
1286    /// # Returns
1287    ///
1288    /// * `Result<(), Error>` - Returns an error if failed to remove users from the group
1289    ///
1290    ///
1291    /// # Example
1292    ///
1293    /// ```no_run
1294    /// use simple_ldap::{LdapClient, LdapConfig};
1295    /// use url::Url;
1296    /// use std::collections::HashSet;
1297    ///
1298    /// #[tokio::main]
1299    /// async fn main(){
1300    ///     let ldap_config = LdapConfig {
1301    ///         bind_dn: String::from("cn=manager"),
1302    ///         bind_password: String::from("password"),
1303    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1304    ///         dn_attribute: None,
1305    ///         connection_settings: None
1306    ///     };
1307    ///
1308    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
1309    ///
1310    ///     let result = client.remove_users_from_group("cn=test_group,ou=groups,dc=example,dc=com",
1311    ///     vec!["uid=bd9b91ec-7a69-4166-bf67-cc7e553b2fd9,ou=people,dc=example,dc=com"]).await;
1312    /// }
1313    /// ```
1314    pub async fn remove_users_from_group(
1315        &mut self,
1316        group_dn: &str,
1317        users: Vec<&str>,
1318    ) -> Result<(), Error> {
1319        let mut mods = Vec::new();
1320        let users = users.iter().copied().collect::<HashSet<&str>>();
1321        mods.push(Mod::Delete("member", users));
1322        let res = self.ldap.modify(group_dn, mods).await;
1323        if let Err(err) = res {
1324            return Err(Error::Update(
1325                format!("Error removing users from group:{group_dn:?}: {err:?}"),
1326                err,
1327            ));
1328        }
1329
1330        let res = res.unwrap().success();
1331        if let Err(err) = res {
1332            match err {
1333                LdapError::LdapResult { result } => {
1334                    if result.rc == NO_SUCH_RECORD {
1335                        return Err(Error::NotFound(format!(
1336                            "No records found for the uid: {group_dn:?}"
1337                        )));
1338                    }
1339                }
1340                _ => {
1341                    return Err(Error::Update(
1342                        format!("Error removing users from group:{group_dn:?}: {err:?}"),
1343                        err,
1344                    ));
1345                }
1346            }
1347        }
1348        Ok(())
1349    }
1350
1351    ///
1352    /// Get the groups associated with a user in the LDAP server. The user will be searched in the provided base DN.
1353    ///
1354    /// # Arguments
1355    ///
1356    /// * `group_ou` - The ou to search for the groups
1357    /// * `user_dn` - The dn of the user
1358    ///
1359    /// # Returns
1360    ///
1361    /// * `Result<Vec<String>, Error>` - Returns a vector of group names
1362    ///
1363    ///
1364    /// # Example
1365    ///
1366    /// ```no_run
1367    /// use simple_ldap::{LdapClient, LdapConfig};
1368    /// use url::Url;
1369    ///
1370    /// #[tokio::main]
1371    /// async fn main(){
1372    ///     let ldap_config = LdapConfig {
1373    ///         bind_dn: String::from("cn=manager"),
1374    ///         bind_password: String::from("password"),
1375    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1376    ///         dn_attribute: None,
1377    ///         connection_settings: None
1378    ///     };
1379    ///
1380    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
1381    ///
1382    ///     let result = client.get_associtated_groups("ou=groups,dc=example,dc=com",
1383    ///     "uid=bd9b91ec-7a69-4166-bf67-cc7e553b2fd9,ou=people,dc=example,dc=com").await;
1384    /// }
1385    /// ```
1386    pub async fn get_associtated_groups(
1387        &mut self,
1388        group_ou: &str,
1389        user_dn: &str,
1390    ) -> Result<Vec<String>, Error> {
1391        let group_filter = Box::new(EqFilter::from(
1392            "objectClass".to_string(),
1393            "groupOfNames".to_string(),
1394        ));
1395
1396        let user_filter = Box::new(EqFilter::from("member".to_string(), user_dn.to_string()));
1397        let mut filter = AndFilter::default();
1398        filter.add(group_filter);
1399        filter.add(user_filter);
1400
1401        let search = self
1402            .ldap
1403            .search(
1404                group_ou,
1405                Scope::Subtree,
1406                filter.filter().as_str(),
1407                vec!["cn"],
1408            )
1409            .await;
1410
1411        if let Err(error) = search {
1412            return Err(Error::Query(
1413                format!("Error searching for record: {error:?}"),
1414                error,
1415            ));
1416        }
1417        let result = search.unwrap().success();
1418        if let Err(error) = result {
1419            return Err(Error::Query(
1420                format!("Error searching for record: {error:?}"),
1421                error,
1422            ));
1423        }
1424
1425        let records = result.unwrap().0;
1426
1427        if records.is_empty() {
1428            return Err(Error::NotFound(String::from(
1429                "User does not belong to any groups",
1430            )));
1431        }
1432
1433        let record = records
1434            .iter()
1435            .map(|record| SearchEntry::construct(record.to_owned()))
1436            .map(|se| se.attrs)
1437            .flat_map(|att| {
1438                att.get("cn")
1439                    .unwrap()
1440                    .iter()
1441                    .map(|x| x.to_owned())
1442                    .collect::<Vec<String>>()
1443            })
1444            .collect::<Vec<String>>();
1445
1446        Ok(record)
1447    }
1448}
1449
1450/// Empty vec becomes None, otherwise it gets wrapped in Some.
1451fn vec_to_option<T>(vec: Vec<T>) -> Option<Vec<T>> {
1452    if vec.is_empty() { None } else { Some(vec) }
1453}
1454
1455/// A proxy type for deriving `Serialize` for `ldap3::SearchEntry`.
1456/// https://serde.rs/remote-derive.html
1457#[derive(Serialize)]
1458#[serde(remote = "ldap3::SearchEntry")]
1459struct Ldap3SearchEntry {
1460    /// Entry DN.
1461    pub dn: String,
1462    /// Attributes.
1463    /// Flattening to ease up the serialization step.
1464    #[serde(flatten)]
1465    pub attrs: HashMap<String, Vec<String>>,
1466    /// Binary-valued attributes.
1467    /// Flattening to ease up the serialization step.
1468    #[serde(flatten)]
1469    pub bin_attrs: HashMap<String, Vec<Vec<u8>>>,
1470}
1471
1472/// This is needed for invoking the deserialize impl directly.
1473/// https://serde.rs/remote-derive.html#invoking-the-remote-impl-directly
1474#[derive(Serialize)]
1475#[serde(transparent)]
1476struct SerializeWrapper(#[serde(with = "Ldap3SearchEntry")] ldap3::SearchEntry);
1477
1478// Allowing users to debug serialization issues from the logs.
1479#[instrument(level = Level::DEBUG)]
1480fn to_signle_value<T: for<'a> Deserialize<'a>>(search_entry: SearchEntry) -> Result<T, Error> {
1481    let string_attributes = search_entry
1482        .attrs
1483        .into_iter()
1484        .filter(|(_, value)| !value.is_empty())
1485        .map(|(arrta, value)| {
1486            if value.len() > 1 {
1487                warn!("Treating multivalued attribute {arrta} as singlevalued.")
1488            }
1489            (Value::String(arrta), map_to_single_value(value.first()))
1490        });
1491
1492    let binary_attributes = search_entry
1493        .bin_attrs
1494        .into_iter()
1495        // I wonder if it's possible to have empties here..?
1496        .filter(|(_, value)| !value.is_empty())
1497        .map(|(arrta, value)| {
1498            if value.len() > 1 {
1499                warn!("Treating multivalued attribute {arrta} as singlevalued.")
1500            }
1501            (
1502                Value::String(arrta),
1503                map_to_single_value_bin(value.first().cloned()),
1504            )
1505        });
1506
1507    // DN is always returned.
1508    // Adding it to the serialized fields as well.
1509    let dn_iter = iter::once(search_entry.dn)
1510        .map(|dn| (Value::String(String::from("dn")), Value::String(dn)));
1511
1512    let all_fields = string_attributes
1513        .chain(binary_attributes)
1514        .chain(dn_iter)
1515        .collect();
1516
1517    let value = serde_value::Value::Map(all_fields);
1518
1519    T::deserialize(value)
1520        .map_err(|err| Error::Mapping(format!("Error converting search result to object, {err:?}")))
1521}
1522
1523#[instrument(level = Level::DEBUG)]
1524fn to_value<T: for<'a> Deserialize<'a>>(search_entry: SearchEntry) -> Result<T, Error> {
1525    let string_attributes = search_entry
1526        .attrs
1527        .into_iter()
1528        .filter(|(_, value)| !value.is_empty())
1529        .map(|(arrta, value)| {
1530            if value.len() == 1 {
1531                return (Value::String(arrta), map_to_single_value(value.first()));
1532            }
1533            (Value::String(arrta), map_to_multi_value(value))
1534        });
1535
1536    let binary_attributes = search_entry
1537        .bin_attrs
1538        .into_iter()
1539        // I wonder if it's possible to have empties here..?
1540        .filter(|(_, value)| !value.is_empty())
1541        .map(|(arrta, value)| {
1542            if value.len() > 1 {
1543                //#TODO: This is a bit of a hack to get multi-valued attributes to work for non binary values. SHOULD fix this.
1544                warn!("Treating multivalued attribute {arrta} as singlevalued.")
1545            }
1546            (
1547                Value::String(arrta),
1548                map_to_single_value_bin(value.first().cloned()),
1549            )
1550            // if value.len() == 1 {
1551            //     return (
1552            //         Value::String(arrta),
1553            //         map_to_single_value_bin(value.first().cloned()),
1554            //     );
1555            // }
1556            // (Value::String(arrta), map_to_multi_value_bin(value))
1557        });
1558
1559    // DN is always returned.
1560    // Adding it to the serialized fields as well.
1561    let dn_iter = iter::once(search_entry.dn)
1562        .map(|dn| (Value::String(String::from("dn")), Value::String(dn)));
1563
1564    let all_fields = string_attributes
1565        .chain(binary_attributes)
1566        .chain(dn_iter)
1567        .collect();
1568
1569    let value = serde_value::Value::Map(all_fields);
1570
1571    T::deserialize(value)
1572        .map_err(|err| Error::Mapping(format!("Error converting search result to object, {err:?}")))
1573}
1574
1575fn map_to_multi_value(attra_value: Vec<String>) -> serde_value::Value {
1576    serde_value::Value::Seq(
1577        attra_value
1578            .iter()
1579            .map(|value| serde_value::Value::String(value.to_string()))
1580            .collect(),
1581    )
1582}
1583
1584fn map_to_multi_value_bin(attra_values: Vec<Vec<u8>>) -> serde_value::Value {
1585    let value_bytes = attra_values
1586        .iter()
1587        .map(|value| {
1588            value
1589                .iter()
1590                .map(|byte| Value::U8(*byte))
1591                .collect::<Vec<Value>>()
1592        })
1593        .map(serde_value::Value::Seq)
1594        .collect::<Vec<Value>>();
1595
1596    serde_value::Value::Seq(value_bytes)
1597}
1598
1599// Allowing users to debug serialization issues from the logs.
1600#[instrument(level = Level::DEBUG)]
1601fn to_multi_value<T: for<'a> Deserialize<'a>>(search_entry: SearchEntry) -> Result<T, Error> {
1602    let value = serde_value::to_value(SerializeWrapper(search_entry)).map_err(|err| {
1603        Error::Mapping(format!("Error converting search result to object, {err:?}"))
1604    })?;
1605
1606    T::deserialize(value)
1607        .map_err(|err| Error::Mapping(format!("Error converting search result to object, {err:?}")))
1608}
1609
1610fn map_to_single_value(attra_value: Option<&String>) -> serde_value::Value {
1611    match attra_value {
1612        Some(value) => serde_value::Value::String(value.to_string()),
1613        None => serde_value::Value::Option(Option::None),
1614    }
1615}
1616
1617fn map_to_single_value_bin(attra_values: Option<Vec<u8>>) -> serde_value::Value {
1618    match attra_values {
1619        Some(bytes) => {
1620            let value_bytes = bytes.into_iter().map(Value::U8).collect();
1621
1622            serde_value::Value::Seq(value_bytes)
1623        }
1624        None => serde_value::Value::Option(Option::None),
1625    }
1626}
1627
1628/// This wrapper exists solely for the purpose of runnig some cleanup in `drop()`.
1629///
1630/// This should be refactored to implement `AsyncDrop` when it gets stabilized:
1631/// https://github.com/rust-lang/rust/issues/126482
1632struct StreamDropWrapper<'a, S, A>
1633where
1634    S: AsRef<str> + Send + Sync + 'a,
1635    A: AsRef<[S]> + Send + Sync + 'a,
1636{
1637    pub search_stream: SearchStream<'a, S, A>,
1638}
1639
1640impl<'a, S, A> Drop for StreamDropWrapper<'a, S, A>
1641where
1642    S: AsRef<str> + Send + Sync + 'a,
1643    A: AsRef<[S]> + Send + Sync + 'a,
1644{
1645    fn drop(&mut self) {
1646        // Making this blocking call in drop is suboptimal.
1647        // We should use async-drop, when it's stabilized:
1648        // https://github.com/rust-lang/rust/issues/126482
1649        block_on(self.cleanup());
1650    }
1651}
1652
1653impl<'a, S, A> StreamDropWrapper<'a, S, A>
1654where
1655    S: AsRef<str> + Send + Sync + 'a,
1656    A: AsRef<[S]> + Send + Sync + 'a,
1657{
1658    ///
1659    /// Cleanup the stream. This method should be called when dropping the stream.
1660    ///
1661    /// This method will cleanup the stream and close the connection.
1662    ///
1663    ///
1664    /// # Errors
1665    ///
1666    /// No errors are returned, as this is meant to be called from `drop()`.
1667    /// Traces are emitted though.
1668    ///
1669    #[instrument(level = Level::TRACE, skip_all)]
1670    async fn cleanup(&mut self) -> () {
1671        // Calling this might not be strictly necessary,
1672        // but it's probably expected so let's just do it.
1673        // I don't think this does any networking most of the time.
1674        let finish_result = self.search_stream.finish().await;
1675
1676        match finish_result.success() {
1677            Ok(_) => (), // All good.
1678            // This is returned if the stream is cancelled in the middle.
1679            // Which is fine for us.
1680            // https://ldap.com/ldap-result-code-reference-client-side-result-codes/#rc-userCanceled
1681            Err(LdapError::LdapResult {
1682                result: LdapResult { rc: 88, .. },
1683            }) => (),
1684            Err(finish_err) => error!("The stream finished with an error: {finish_err}"),
1685        }
1686
1687        match self.search_stream.state() {
1688            // Stream processed to the end, no need to cancel the operation.
1689            // This should be the common case.
1690            StreamState::Done | StreamState::Closed => (),
1691            StreamState::Error => {
1692                error!(
1693                    "Stream is in Error state. Not trying to cancel it as it could do more harm than good."
1694                );
1695            }
1696            StreamState::Fresh | StreamState::Active => {
1697                info!("Stream is still open. Issuing cancellation to the server.");
1698                let msgid = self.search_stream.ldap_handle().last_id();
1699                let result = self.search_stream.ldap_handle().abandon(msgid).await;
1700
1701                match result {
1702                    Ok(_) => (),
1703                    Err(err) => {
1704                        error!("Error abandoning search result: {:?}", err);
1705                        ()
1706                    }
1707                }
1708            }
1709        }
1710    }
1711}
1712
1713/// A helper to create native rust streams out of `ldap3::SearchStream`s.
1714fn to_native_stream<'a, S, A>(
1715    ldap3_stream: SearchStream<'a, S, A>,
1716) -> Result<impl Stream<Item = Result<Record, Error>> + 'a + use<'a, S, A>, Error>
1717where
1718    S: AsRef<str> + Send + Sync + 'a,
1719    A: AsRef<[S]> + Send + Sync + 'a,
1720{
1721    // This will handle stream cleanup.
1722    let stream_wrapper = StreamDropWrapper {
1723        search_stream: ldap3_stream,
1724    };
1725
1726    // Produce the steam itself by unfolding.
1727    let stream = stream::try_unfold(stream_wrapper, async |mut search| {
1728        match search.search_stream.next().await {
1729            // In the middle of the stream. Produce the next result.
1730            Ok(Some(result_entry)) => Ok(Some((
1731                Record {
1732                    search_entry: SearchEntry::construct(result_entry),
1733                },
1734                search,
1735            ))),
1736            // Stream is done.
1737            Ok(None) => Ok(None),
1738            Err(ldap_error) => Err(Error::Query(
1739                format!("Error getting next record: {ldap_error:?}"),
1740                ldap_error,
1741            )),
1742        }
1743    });
1744
1745    Ok(stream)
1746}
1747
1748/// The Record struct is used to map the search result to a struct.
1749/// The Record struct has a method to_record which will map the search result to a struct.
1750/// The Record struct has a method to_multi_valued_record which will map the search result to a struct with multi valued attributes.
1751//
1752// It would be nice to hide this record type from the public API and just expose already
1753// deserialized user types.
1754pub struct Record {
1755    search_entry: SearchEntry,
1756}
1757
1758impl Record {
1759    ///
1760    /// Create a new Record object with single valued attributes.
1761    /// This is essentially parsing the response records into usable types.
1762    //
1763    // This is kind of misnomer, as we aren't creating records here.
1764    // Perhaps something like "deserialize" would fit better?
1765    pub fn to_record<T: for<'b> serde::Deserialize<'b>>(self) -> Result<T, Error> {
1766        to_value(self.search_entry)
1767    }
1768
1769    #[deprecated(
1770        since = "6.0.0",
1771        note = "Use to_record instead. This method is deprecated and will be removed in future versions."
1772    )]
1773    pub fn to_multi_valued_record_<T: for<'b> serde::Deserialize<'b>>(self) -> Result<T, Error> {
1774        to_multi_value(self.search_entry)
1775    }
1776}
1777
1778pub enum StreamResult<T> {
1779    Record(T),
1780    Done,
1781    Finished,
1782}
1783
1784///
1785/// The error type for the LDAP client
1786///
1787#[derive(Debug, Error)]
1788pub enum Error {
1789    /// Error occurred when performing a LDAP query
1790    #[error("{0}")]
1791    Query(String, #[source] LdapError),
1792    /// No records found for the search criteria
1793    #[error("{0}")]
1794    NotFound(String),
1795    /// Multiple records found for the search criteria
1796    #[error("{0}")]
1797    MultipleResults(String),
1798    /// Authenticating a user failed.
1799    #[error("{0}")]
1800    AuthenticationFailed(String),
1801    /// Error occurred when creating a record
1802    #[error("{0}")]
1803    Create(String, #[source] LdapError),
1804    /// Error occurred when updating a record
1805    #[error("{0}")]
1806    Update(String, #[source] LdapError),
1807    /// Error occurred when deleting a record
1808    #[error("{0}")]
1809    Delete(String, #[source] LdapError),
1810    /// Error occurred when mapping the search result to a struct
1811    #[error("{0}")]
1812    Mapping(String),
1813    /// Error occurred while attempting to create an LDAP connection
1814    #[error("{0}")]
1815    Connection(String, #[source] LdapError),
1816    /// Error occurred while attempting to close an LDAP connection.
1817    /// Includes unbind issues.
1818    #[error("{0}")]
1819    Close(String, #[source] LdapError),
1820    /// Error occurred while abandoning the search result
1821    #[error("{0}")]
1822    Abandon(String, #[source] LdapError),
1823
1824    /// Something wrong with Server Side Sort
1825    #[error("{0}")]
1826    Sort(String),
1827}
1828
1829#[cfg(test)]
1830mod tests {
1831    //! Local tests that don't need to connect to a server.
1832
1833    use super::*;
1834    use anyhow::anyhow;
1835    use serde::Deserialize;
1836    use serde_with::OneOrMany;
1837    use serde_with::serde_as;
1838    use uuid::Uuid;
1839
1840    #[test]
1841    fn create_multi_value_test() {
1842        let mut map: HashMap<String, Vec<String>> = HashMap::new();
1843        map.insert(
1844            "key1".to_string(),
1845            vec!["value1".to_string(), "value2".to_string()],
1846        );
1847        map.insert(
1848            "key2".to_string(),
1849            vec!["value3".to_string(), "value4".to_string()],
1850        );
1851
1852        let dn = "CN=Thing,OU=Unit,DC=example,DC=org";
1853        let entry = SearchEntry {
1854            dn: dn.to_string(),
1855            attrs: map,
1856            bin_attrs: HashMap::new(),
1857        };
1858
1859        let test = to_multi_value::<TestMultiValued>(entry);
1860
1861        let test = test.unwrap();
1862        assert_eq!(test.key1, vec!["value1".to_string(), "value2".to_string()]);
1863        assert_eq!(test.key2, vec!["value3".to_string(), "value4".to_string()]);
1864        assert_eq!(test.dn, dn);
1865    }
1866
1867    #[test]
1868    fn create_single_value_test() {
1869        let mut map: HashMap<String, Vec<String>> = HashMap::new();
1870        map.insert("key1".to_string(), vec!["value1".to_string()]);
1871        map.insert("key2".to_string(), vec!["value2".to_string()]);
1872        map.insert("key4".to_string(), vec!["value4".to_string()]);
1873
1874        let dn = "CN=Thing,OU=Unit,DC=example,DC=org";
1875
1876        let entry = SearchEntry {
1877            dn: dn.to_string(),
1878            attrs: map,
1879            bin_attrs: HashMap::new(),
1880        };
1881
1882        let test = to_signle_value::<TestSingleValued>(entry);
1883
1884        let test = test.unwrap();
1885        assert_eq!(test.key1, "value1".to_string());
1886        assert_eq!(test.key2, "value2".to_string());
1887        assert!(test.key3.is_none());
1888        assert_eq!(test.key4.unwrap(), "value4".to_string());
1889        assert_eq!(test.dn, dn);
1890    }
1891
1892    #[test]
1893    fn create_to_value_string_test() {
1894        let mut map: HashMap<String, Vec<String>> = HashMap::new();
1895        map.insert("key1".to_string(), vec!["value1".to_string()]);
1896        map.insert("key2".to_string(), vec!["value2".to_string()]);
1897        map.insert("key4".to_string(), vec!["value4".to_string()]);
1898        map.insert(
1899            "key5".to_string(),
1900            vec!["value5".to_string(), "value6".to_string()],
1901        );
1902
1903        let dn = "CN=Thing,OU=Unit,DC=example,DC=org";
1904
1905        let entry = SearchEntry {
1906            dn: dn.to_string(),
1907            attrs: map,
1908            bin_attrs: HashMap::new(),
1909        };
1910
1911        let test = to_value::<TestValued>(entry);
1912
1913        let test = test.unwrap();
1914        assert_eq!(test.key1, "value1".to_string());
1915        assert!(test.key3.is_none());
1916        let key4 = test.key4;
1917        assert_eq!(key4[0], "value4".to_string());
1918        let key5 = test.key5;
1919        assert_eq!(key5[0], "value5".to_string());
1920        assert_eq!(key5[1], "value6".to_string());
1921
1922        assert_eq!(test.dn, dn);
1923    }
1924
1925    #[test]
1926    fn binary_single_to_value_test() -> anyhow::Result<()> {
1927        #[derive(Deserialize)]
1928        struct TestMultivalueBinary {
1929            pub uuids: Uuid,
1930            pub key1: String,
1931        }
1932
1933        let (bytes, correct_string_representation) = get_binary_uuid();
1934
1935        let entry = SearchEntry {
1936            dn: String::from("CN=Thing,OU=Unit,DC=example,DC=org"),
1937            attrs: HashMap::from([(String::from("key1"), vec![String::from("value1")])]),
1938            bin_attrs: HashMap::from([(String::from("uuids"), vec![bytes])]),
1939        };
1940
1941        let test = to_value::<TestMultivalueBinary>(entry).unwrap();
1942
1943        let string_uuid = test.uuids.hyphenated().to_string();
1944        assert_eq!(string_uuid, correct_string_representation);
1945        Ok(())
1946    }
1947
1948    // #[test] // This test is not working, because the OneOrMany trait is not implemented for Uuid. Will fix this later.
1949    fn binary_multi_to_value_test() -> anyhow::Result<()> {
1950        #[serde_as]
1951        #[derive(Deserialize)]
1952        struct TestMultivalueBinary {
1953            #[serde_as(as = "OneOrMany<_>")]
1954            pub uuids: Vec<Uuid>,
1955            pub key1: String,
1956        }
1957
1958        let (bytes, correct_string_representation) = get_binary_uuid();
1959
1960        let entry = SearchEntry {
1961            dn: String::from("CN=Thing,OU=Unit,DC=example,DC=org"),
1962            attrs: HashMap::from([(String::from("key1"), vec![String::from("value1")])]),
1963            bin_attrs: HashMap::from([(String::from("uuids"), vec![bytes])]),
1964        };
1965
1966        let test = to_value::<TestMultivalueBinary>(entry).unwrap();
1967
1968        match test.uuids.as_slice() {
1969            [one] => {
1970                let string_uuid = one.hyphenated().to_string();
1971                assert_eq!(string_uuid, correct_string_representation);
1972                Ok(())
1973            }
1974            [..] => Err(anyhow!("There was supposed to be exactly one uuid.")),
1975        }
1976    }
1977
1978    #[derive(Debug, Deserialize)]
1979    struct TestMultiValued {
1980        dn: String,
1981        key1: Vec<String>,
1982        key2: Vec<String>,
1983    }
1984
1985    #[derive(Debug, Deserialize)]
1986    struct TestSingleValued {
1987        dn: String,
1988        key1: String,
1989        key2: String,
1990        key3: Option<String>,
1991        key4: Option<String>,
1992    }
1993
1994    #[serde_as]
1995    #[derive(Debug, Deserialize)]
1996    struct TestValued {
1997        dn: String,
1998        key1: String,
1999        key3: Option<String>,
2000        #[serde_as(as = "OneOrMany<_>")]
2001        key4: Vec<String>,
2002        #[serde_as(as = "OneOrMany<_>")]
2003        key5: Vec<String>,
2004    }
2005    /// Get the binary and hyphenated string representations of an UUID for testing.
2006    fn get_binary_uuid() -> (Vec<u8>, String) {
2007        // Exaple grabbed from uuid docs:
2008        // https://docs.rs/uuid/latest/uuid/struct.Uuid.html#method.from_bytes
2009        let bytes = vec![
2010            0xa1, 0xa2, 0xa3, 0xa4, 0xb1, 0xb2, 0xc1, 0xc2, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6,
2011            0xd7, 0xd8,
2012        ];
2013
2014        let correct_string_representation = String::from("a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8");
2015
2016        (bytes, correct_string_representation)
2017    }
2018
2019    #[test]
2020    fn deserialize_binary_multi_value_test() -> anyhow::Result<()> {
2021        #[derive(Deserialize)]
2022        struct TestMultivalueBinary {
2023            pub uuids: Vec<Uuid>,
2024        }
2025
2026        let (bytes, correct_string_representation) = get_binary_uuid();
2027
2028        let entry = SearchEntry {
2029            dn: String::from("CN=Thing,OU=Unit,DC=example,DC=org"),
2030            attrs: HashMap::new(),
2031            bin_attrs: HashMap::from([(String::from("uuids"), vec![bytes])]),
2032        };
2033
2034        let record = Record {
2035            search_entry: entry,
2036        };
2037
2038        let deserialized: TestMultivalueBinary = record.to_multi_valued_record_()?;
2039
2040        match deserialized.uuids.as_slice() {
2041            [one] => {
2042                let string_uuid = one.hyphenated().to_string();
2043                assert_eq!(string_uuid, correct_string_representation);
2044                Ok(())
2045            }
2046            [..] => Err(anyhow!("There was supposed to be exactly one uuid.")),
2047        }
2048    }
2049
2050    #[test]
2051    fn deserialize_binary_single_value_test() -> anyhow::Result<()> {
2052        #[derive(Deserialize)]
2053        struct TestSingleValueBinary {
2054            pub uuid: Uuid,
2055        }
2056
2057        let (bytes, correct_string_representation) = get_binary_uuid();
2058
2059        let entry = SearchEntry {
2060            dn: String::from("CN=Thing,OU=Unit,DC=example,DC=org"),
2061            attrs: HashMap::new(),
2062            bin_attrs: HashMap::from([(String::from("uuid"), vec![bytes])]),
2063        };
2064
2065        let record = Record {
2066            search_entry: entry,
2067        };
2068
2069        let deserialized: TestSingleValueBinary = record.to_record()?;
2070
2071        let string_uuid = deserialized.uuid.hyphenated().to_string();
2072        assert_eq!(string_uuid, correct_string_representation);
2073
2074        Ok(())
2075    }
2076}
2077
2078// Add readme examples to doctests:
2079// https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html#include-items-only-when-collecting-doctests
2080#[doc = include_str!("../README.md")]
2081#[cfg(doctest)]
2082pub struct ReadmeDoctests;