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