simple_ldap/
lib.rs

1//! # simple-ldap
2//!
3//! This is a high-level LDAP client library created by wrapping the rust LDAP3 clinet.
4//! This provides high-level functions that helps to interact with LDAP.
5//!
6//!
7//! ## Features
8//!
9//! - All the usual LDAP operations
10//! - Search result deserialization
11//! - Connection pooling
12//! - Streaming search with native rust [`Stream`](https://docs.rs/futures/latest/futures/stream/trait.Stream.html)s
13//!
14//!
15//! ## Usage
16//!
17//! Adding `simple_ldap` as a dependency to your project:
18//!
19//! ```commandline
20//! cargo add simple-ldap
21//! ```
22//!
23//! Most functionalities are defined on the `LdapClient` type. Have a look at the docs.
24//!
25//!
26//! ### Example
27//!
28//! Examples of individual operations are scattered throughout the docs, but here's the basic usage:
29//!
30//! ```no_run
31//! use simple_ldap::{
32//!     LdapClient, LdapConfig,
33//!     filter::EqFilter,
34//!     ldap3::Scope
35//! };
36//! use url::Url;
37//! use serde::Deserialize;
38//!
39//! // A type for deserializing the search result into.
40//! #[derive(Debug, Deserialize)]
41//! struct User {
42//!     uid: String,
43//!     cn: String,
44//!     sn: String,
45//! }
46//!
47//!
48//! #[tokio::main]
49//! async fn main(){
50//!     let ldap_config = LdapConfig {
51//!         bind_dn: String::from("cn=manager"),
52//!         bind_password: String::from("password"),
53//!         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
54//!         dn_attribute: None,
55//!         connection_settings: None
56//!     };
57//!     let mut client = LdapClient::new(ldap_config).await.unwrap();
58//!     let name_filter = EqFilter::from("cn".to_string(), "Sam".to_string());
59//!     let user: User = client
60//!         .search::<User>(
61//!         "ou=people,dc=example,dc=com",
62//!         Scope::OneLevel,
63//!         &name_filter,
64//!         &vec!["cn", "sn", "uid"],
65//!     ).await.unwrap();
66//! }
67//! ```
68//!
69//!
70//! ## Compile time features
71//!
72//! * `tls-native` - (Enabled by default) Enables TLS support using the systems native implementation.
73//! * `tls-rustls` - Enables TLS support using `rustls`. **Conflicts with `tls-native` so you need to disable default features to use this.
74//! * `pool` - Enable connection pooling
75//!
76
77use std::{
78    collections::{HashMap, HashSet},
79    pin::Pin,
80    task::{Context, Poll},
81};
82
83use filter::{AndFilter, EqFilter, Filter};
84use futures::FutureExt;
85use ldap3::{
86    adapters::{Adapter, EntriesOnly, PagedResults},
87    Ldap, LdapConnAsync, LdapConnSettings, LdapError, Mod, Scope, SearchEntry, SearchStream,
88    StreamState,
89};
90use serde::Deserialize;
91use thiserror::Error;
92use tracing::{debug, error};
93use url::Url;
94
95pub mod filter;
96#[cfg(feature = "pool")]
97pub mod pool;
98pub extern crate ldap3;
99
100const LDAP_ENTRY_DN: &str = "entryDN";
101const NO_SUCH_RECORD: u32 = 32;
102
103
104/// Configuration and authentication for LDAP connection
105#[derive(derive_more::Debug, Clone)]
106pub struct LdapConfig {
107    pub ldap_url: Url,
108    /// DistinguishedName, aka the "username" to use for the connection.
109    pub bind_dn: String,
110    #[debug(skip)] // We don't want to print passwords.
111    pub bind_password: String,
112    pub dn_attribute: Option<String>,
113    /// Low level configuration for the connection.
114    /// You can probably skip it.
115    #[debug(skip)] // Debug omitted, because it just doesn't implement it.
116    pub connection_settings: Option<LdapConnSettings>,
117}
118
119
120///
121/// High-level LDAP client wrapper ontop of ldap3 crate. This wrapper provides a high-level interface to perform LDAP operations
122/// including authentication, search, update, delete
123///
124#[derive(Debug, Clone)]
125pub struct LdapClient {
126    /// The internal connection handle.
127    ldap: Ldap,
128    dn_attr: Option<String>,
129}
130
131impl LdapClient {
132    ///
133    /// Creates a new asynchronous LDAP client.s
134    /// It's capable of running multiple operations concurrently.
135    ///
136    /// # Bind
137    ///
138    /// This performs a bind on the connection so need to worry about that.
139    ///
140    pub async fn new(config: LdapConfig) -> Result<Self, Error> {
141        debug!("Creating new connection");
142
143        // With or without connection settings
144        let (conn, mut ldap) = match config.connection_settings {
145            None => LdapConnAsync::from_url(&config.ldap_url).await,
146            Some(settings) => {
147                LdapConnAsync::from_url_with_settings(settings, &config.ldap_url).await
148            }
149        }
150        .map_err(|ldap_err| {
151            Error::Connection(
152                String::from("Failed to initialize LDAP connection."),
153                ldap_err,
154            )
155        })?;
156
157        ldap3::drive!(conn);
158
159        ldap.simple_bind(&config.bind_dn, &config.bind_password)
160            .await
161            .map_err(|ldap_err| Error::Connection(String::from("Bind failed"), ldap_err))?
162            .success()
163            .map_err(|ldap_err| Error::Connection(String::from("Bind failed"), ldap_err))?;
164
165        Ok(Self {
166            dn_attr: config.dn_attribute,
167            ldap,
168        })
169    }
170}
171
172impl LdapClient {
173    /// Returns the ldap3 client
174    #[deprecated = "This abstraction leakage will be removed in a future release.
175                    Use the provided methods instead. If something's missing, open an issue in github."]
176    pub fn get_inner(&self) -> Ldap {
177        self.ldap.clone()
178    }
179
180    /// End the LDAP connection.
181    ///
182    /// **Caution advised!**
183    ///
184    /// This will close the connection for all clones of this client as well,
185    /// including open streams. So make sure that you're really good to close.
186    ///
187    /// Closing an LDAP connection with an unbind is *a curtesy.*
188    /// It's fine to skip it, and because of the async hurdless outlined above,
189    /// I would perhaps even recommend it.
190    // Consuming self to prevent accidental use after unbind.
191    // This also conveniently prevents calling this with pooled clients, as the
192    // wrapper `Object` prohibiths moving.
193    pub async fn unbind(mut self) -> Result<(), Error> {
194        match self.ldap.unbind().await {
195            Ok(_) => Ok(()),
196            Err(error) => Err(Error::Close(String::from("Failed to unbind"), error)),
197        }
198    }
199
200    ///
201    /// The user is authenticated by searching for the user in the LDAP server.
202    /// The search is performed using the provided filter. The filter should be a filter that matches a single user.
203    ///
204    /// # Arguments
205    ///
206    /// * `base` - The base DN to search for the user
207    /// * `uid` - The uid of the user
208    /// * `password` - The password of the user
209    /// * `filter` - The filter to search for the user
210    ///
211    ///
212    /// # Returns
213    ///
214    /// * `Result<(), Error>` - Returns an error if the authentication fails
215    ///
216    ///
217    /// # Example
218    ///
219    /// ```no_run
220    /// use simple_ldap::{
221    ///     LdapClient, LdapConfig,
222    ///     filter::EqFilter
223    /// };
224    /// use url::Url;
225    ///
226    /// #[tokio::main]
227    /// async fn main(){
228    ///     let ldap_config = LdapConfig {
229    ///         bind_dn: String::from("cn=manager"),
230    ///         bind_password: String::from("password"),
231    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
232    ///         dn_attribute: None,
233    ///         connection_settings: None
234    ///     };
235    ///
236    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
237    ///     let name_filter = EqFilter::from("cn".to_string(), "Sam".to_string());
238    ///
239    ///     let result = client.authenticate("", "Sam", "password", Box::new(name_filter)).await;
240    /// }
241    /// ```
242    pub async fn authenticate(
243        &mut self,
244        base: &str,
245        uid: &str,
246        password: &str,
247        filter: Box<dyn Filter>,
248    ) -> Result<(), Error> {
249        let attr_dn = self
250            .dn_attr
251            .as_ref()
252            .map(|a| a.as_str())
253            .unwrap_or(LDAP_ENTRY_DN);
254
255        let rs = self
256            .ldap
257            .search(base, Scope::OneLevel, filter.filter().as_str(), [attr_dn])
258            .await
259            .map_err(|e| Error::Query("Unable to query user for authentication".into(), e))?;
260
261        let (data, _rs) = rs
262            .success()
263            .map_err(|e| Error::Query("Could not find user for authentication".into(), e))?;
264
265        if data.is_empty() {
266            return Err(Error::NotFound(format!("No record found {:?}", uid)));
267        }
268        if data.len() > 1 {
269            return Err(Error::MultipleResults(format!(
270                "Found multiple records for uid {:?}",
271                uid
272            )));
273        }
274
275        let record = data.first().unwrap().to_owned();
276        let record = SearchEntry::construct(record);
277        let result: HashMap<&str, String> = record
278            .attrs
279            .iter()
280            .filter(|(_, value)| !value.is_empty())
281            .map(|(arrta, value)| (arrta.as_str(), value.first().unwrap().clone()))
282            .collect();
283
284        let entry_dn = result.get(attr_dn).ok_or_else(|| {
285            Error::AuthenticationFailed(format!("Unable to retrieve DN of user {uid}"))
286        })?;
287
288        self.ldap
289            .simple_bind(entry_dn, password)
290            .await
291            .map_err(|_| {
292                Error::AuthenticationFailed(format!("Error authenticating user: {:?}", uid))
293            })
294            .and_then(|r| {
295                r.success().map_err(|_| {
296                    Error::AuthenticationFailed(format!("Error authenticating user: {:?}", uid))
297                })
298            })
299            .and(Ok(()))
300    }
301
302    async fn search_innter(
303        &mut self,
304        base: &str,
305        scope: Scope,
306        filter: &(impl Filter + ?Sized),
307        attributes: &Vec<&str>,
308    ) -> Result<SearchEntry, Error> {
309        let search = self
310            .ldap
311            .search(base, scope, filter.filter().as_str(), attributes)
312            .await;
313        if let Err(error) = search {
314            return Err(Error::Query(
315                format!("Error searching for record: {:?}", error),
316                error,
317            ));
318        }
319        let result = search.unwrap().success();
320        if let Err(error) = result {
321            return Err(Error::Query(
322                format!("Error searching for record: {:?}", error),
323                error,
324            ));
325        }
326
327        let records = result.unwrap().0;
328
329        if records.len() > 1 {
330            return Err(Error::MultipleResults(String::from(
331                "Found multiple records for the search criteria",
332            )));
333        }
334
335        if records.is_empty() {
336            return Err(Error::NotFound(String::from(
337                "No records found for the search criteria",
338            )));
339        }
340
341        let record = records.first().unwrap();
342
343        Ok(SearchEntry::construct(record.to_owned()))
344    }
345
346    ///
347    /// Search a single value from the LDAP server. The search is performed using the provided filter.
348    /// The filter should be a filter that matches a single record. if the filter matches multiple users, an error is returned.
349    /// This operatrion is useful when records has single value attributes.
350    /// Result will be mapped to a struct of type T.
351    ///
352    /// # Arguments
353    ///
354    /// * `base` - The base DN to search for the user
355    /// * `scope` - The scope of the search
356    /// * `filter` - The filter to search for the user
357    /// * `attributes` - The attributes to return from the search
358    ///
359    ///
360    /// # Returns
361    ///
362    /// * `Result<T, Error>` - The result will be mapped to a struct of type T
363    ///
364    ///
365    /// # Example
366    ///
367    /// ```no_run
368    /// use simple_ldap::{
369    ///     LdapClient, LdapConfig,
370    ///     filter::EqFilter,
371    ///     ldap3::Scope
372    /// };
373    /// use url::Url;
374    /// use serde::Deserialize;
375    ///
376    ///
377    /// #[derive(Debug, Deserialize)]
378    /// struct User {
379    ///     uid: String,
380    ///     cn: String,
381    ///     sn: String,
382    /// }
383    ///
384    /// #[tokio::main]
385    /// async fn main(){
386    ///     let ldap_config = LdapConfig {
387    ///         bind_dn: String::from("cn=manager"),
388    ///         bind_password: String::from("password"),
389    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
390    ///         dn_attribute: None,
391    ///         connection_settings: None
392    ///     };
393    ///
394    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
395    ///
396    ///     let name_filter = EqFilter::from("cn".to_string(), "Sam".to_string());
397    ///     let user_result = client
398    ///         .search::<User>(
399    ///         "ou=people,dc=example,dc=com",
400    ///         Scope::OneLevel,
401    ///         &name_filter,
402    ///         &vec!["cn", "sn", "uid"],
403    ///     ).await;
404    /// }
405    /// ```
406    ///
407    pub async fn search<T: for<'a> serde::Deserialize<'a>>(
408        &mut self,
409        base: &str,
410        scope: Scope,
411        filter: &impl Filter,
412        attributes: &Vec<&str>,
413    ) -> Result<T, Error> {
414        let search_entry = self.search_innter(base, scope, filter, attributes).await?;
415        to_signle_value(search_entry)
416    }
417
418    ///
419    /// Search a single value from the LDAP server. The search is performed using the provided filter.
420    /// The filter should be a filter that matches a single record. if the filter matches multiple users, an error is returned.
421    /// This operatrion is useful when records has multi-valued attributes.
422    ///
423    /// # Arguments
424    ///
425    /// * `base` - The base DN to search for the user
426    /// * `scope` - The scope of the search
427    /// * `filter` - The filter to search for the user
428    /// * `attributes` - The attributes to return from the search
429    ///
430    ///
431    /// # Returns
432    ///
433    /// * `Result<T, Error>` - The result will be mapped to a struct of type T
434    ///
435    ///
436    /// # Example
437    ///
438    /// ```no_run
439    /// use simple_ldap::{
440    ///     LdapClient, LdapConfig,
441    ///     filter::EqFilter,
442    ///     ldap3::Scope
443    /// };
444    /// use url::Url;
445    /// use serde::Deserialize;
446    ///
447    ///
448    /// #[derive(Debug, Deserialize)]
449    /// struct TestMultiValued {
450    ///    key1: Vec<String>,
451    ///    key2: Vec<String>,
452    /// }
453    ///
454    /// #[tokio::main]
455    /// async fn main(){
456    ///     let ldap_config = LdapConfig {
457    ///         bind_dn: String::from("cn=manager"),
458    ///         bind_password: String::from("password"),
459    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
460    ///         dn_attribute: None,
461    ///         connection_settings: None
462    ///     };
463    ///
464    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
465    ///
466    ///     let name_filter = EqFilter::from("cn".to_string(), "Sam".to_string());
467    ///     let user_result = client.search_multi_valued::<TestMultiValued>(
468    ///         "",
469    ///         Scope::OneLevel,
470    ///         &name_filter,
471    ///         &vec!["cn", "sn", "uid"]
472    ///     ).await;
473    /// }
474    /// ```
475    ///
476    pub async fn search_multi_valued<T: for<'a> serde::Deserialize<'a>>(
477        &mut self,
478        base: &str,
479        scope: Scope,
480        filter: &impl Filter,
481        attributes: &Vec<&str>,
482    ) -> Result<T, Error> {
483        let search_entry = self.search_innter(base, scope, filter, attributes).await?;
484        to_multi_value(search_entry)
485    }
486
487    async fn streaming_search_inner<'a>(
488        &mut self,
489        base: &'a str,
490        scope: Scope,
491        filter: &'a (impl Filter + ?Sized),
492        attributes: &'a Vec<&'a str>,
493    ) -> Result<Stream<'a, &'a str, &'a Vec<&'a str>>, Error> {
494        let search_stream: Result<SearchStream<'_, &str, &Vec<&str>>, LdapError> = self
495            .ldap
496            .streaming_search(base, scope, filter.filter().as_str(), attributes)
497            .await;
498        if let Err(error) = search_stream {
499            return Err(Error::Query(
500                format!("Error searching for record: {:?}", error),
501                error,
502            ));
503        }
504
505        let search_stream = search_stream.unwrap();
506
507        let stream = Stream::new(search_stream);
508        Ok(stream)
509    }
510
511    ///
512    /// This method is used to search multiple records from the LDAP server. The search is performed using the provided filter.
513    /// Method will return a Stream. The stream will lazily fetch batches of results resulting in a smaller
514    /// memory footprint.
515    ///
516    /// Prefer streaming search if you don't know that the result set is going to be small.
517    ///
518    ///
519    /// # Arguments
520    ///
521    /// * `base` - The base DN to search for the user
522    /// * `scope` - The scope of the search
523    /// * `filter` - The filter to search for the user
524    /// * `attributes` - The attributes to return from the search
525    ///
526    ///
527    /// # Returns
528    ///
529    /// * `Result<Stream, Error>` - The result will be mapped to a Stream. **Remember to `cleanup()` it when you're done!**
530    ///
531    ///
532    /// # Example
533    ///
534    /// ```no_run
535    /// use simple_ldap::{
536    ///     LdapClient, LdapConfig,
537    ///     filter::EqFilter,
538    ///     ldap3::Scope
539    /// };
540    /// use url::Url;
541    /// use serde::Deserialize;
542    /// use futures::StreamExt;
543    ///
544    ///
545    /// #[derive(Deserialize, Debug)]
546    /// struct User {
547    ///     uid: String,
548    ///     cn: String,
549    ///     sn: String,
550    /// }
551    ///
552    /// #[tokio::main]
553    /// async fn main(){
554    ///     let ldap_config = LdapConfig {
555    ///         bind_dn: String::from("cn=manager"),
556    ///         bind_password: String::from("password"),
557    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
558    ///         dn_attribute: None,
559    ///         connection_settings: None
560    ///     };
561    ///
562    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
563    ///
564    ///     let name_filter = EqFilter::from(String::from("cn"), String::from("Sam"));
565    ///     let attributes = vec!["cn", "sn", "uid"];
566    ///
567    ///     let mut stream = client.streaming_search(
568    ///         "",
569    ///         Scope::OneLevel,
570    ///         &name_filter,
571    ///         &attributes
572    ///     ).await.unwrap();
573    ///
574    ///     while let Some(result) = stream.next().await {
575    ///         match result {
576    ///             Ok(element) => {
577    ///                 let user: User = element.to_record().unwrap();
578    ///                 println!("User: {:?}", user);
579    ///             }
580    ///             Err(err) => {
581    ///                 println!("Error: {:?}", err);
582    ///             }
583    ///         }
584    ///     }
585    ///     stream.cleanup().await;
586    /// }
587    /// ```
588    ///
589    pub async fn streaming_search<'a>(
590        // This self reference  lifetime has some nuance behind it.
591        //
592        // In principle it could just be a value, but then you wouldn't be able to call this
593        // with a pooled client, as the deadpool `Object` wrapper only ever gives out references.
594        //
595        // The lifetime is needed to guarantee that the client is not returned to the pool before
596        // the returned stream is finished. This requirement is artificial. Internally the `ldap3` client
597        // just makes copy. So this lifetime is here just to enforce correct pool usage.
598        &'a mut self,
599        base: &'a str,
600        scope: Scope,
601        filter: &'a impl Filter,
602        attributes: &'a Vec<&'a str>,
603    ) -> Result<Stream<'a, &'a str, &'a Vec<&'a str>>, Error> {
604        let entry = self
605            .streaming_search_inner(base, scope, filter, attributes)
606            .await?;
607
608        Ok(entry)
609    }
610
611    ///
612    /// This method is used to search multiple records from the LDAP server and results will be paginated.
613    /// Method will return a Stream. The stream will lazily fetch batches of results resulting in a smaller
614    /// memory footprint.
615    ///
616    /// Prefer streaming search if you don't know that the result set is going to be small.
617    ///
618    ///
619    /// # Arguments
620    ///
621    /// * `base` - The base DN to search for the user
622    /// * `scope` - The scope of the search
623    /// * `filter` - The filter to search for the user
624    /// * `page_size` - The maximum number of records in a page
625    /// * `attributes` - The attributes to return from the search
626    ///
627    ///
628    /// # Returns
629    ///
630    /// * `Result<Stream, Error>` - A stream that can be used to iterate through the search results.
631    ///
632    ///
633    /// # Example
634    ///
635    /// ```no_run
636    /// use simple_ldap::{
637    ///     LdapClient, LdapConfig,
638    ///     filter::EqFilter,
639    ///     ldap3::Scope
640    /// };
641    /// use url::Url;
642    /// use serde::Deserialize;
643    /// use futures::StreamExt;
644    ///
645    ///
646    /// #[derive(Deserialize, Debug)]
647    /// struct User {
648    ///     uid: String,
649    ///     cn: String,
650    ///     sn: String,
651    /// }
652    ///
653    /// #[tokio::main]
654    /// async fn main(){
655    ///     let ldap_config = LdapConfig {
656    ///         bind_dn: String::from("cn=manager"),
657    ///         bind_password: String::from("password"),
658    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
659    ///         dn_attribute: None,
660    ///         connection_settings: None
661    ///     };
662    ///
663    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
664    ///
665    ///     let name_filter = EqFilter::from(String::from("cn"), String::from("Sam"));
666    ///     let attributes = vec!["cn", "sn", "uid"];
667    ///
668    ///     let mut stream = client.streaming_search_with(
669    ///         "",
670    ///         Scope::OneLevel,
671    ///         &name_filter,
672    ///         &attributes,
673    ///         200 // The pagesize
674    ///     ).await.unwrap();
675    ///
676    ///     while let Some(result) = stream.next().await {
677    ///         match result {
678    ///             Ok(element) => {
679    ///                 let user: User = element.to_record().unwrap();
680    ///                 println!("User: {:?}", user);
681    ///             }
682    ///             Err(err) => {
683    ///                 println!("Error: {:?}", err);
684    ///             }
685    ///         }
686    ///     }
687    ///     stream.cleanup().await;
688    /// }
689    /// ```
690    ///
691    pub async fn streaming_search_with<'a>(
692        // This self reference  lifetime has some nuance behind it.
693        //
694        // In principle it could just be a value, but then you wouldn't be able to call this
695        // with a pooled client, as the deadpool `Object` wrapper only ever gives out references.
696        //
697        // The lifetime is needed to guarantee that the client is not returned to the pool before
698        // the returned stream is finished. This requirement is artificial. Internally the `ldap3` client
699        // just makes copy. So this lifetime is here just to enforce correct pool usage.
700        &'a mut self,
701        base: &'a str,
702        scope: Scope,
703        filter: &'a (impl Filter + ?Sized),
704        attributes: &'a Vec<&'a str>,
705        page_size: i32,
706    ) -> Result<Stream<'a, &'a str, &'a Vec<&'a str>>, Error> {
707        let adapters: Vec<Box<dyn Adapter<_, _>>> = vec![
708            Box::new(EntriesOnly::new()),
709            Box::new(PagedResults::new(page_size)),
710        ];
711        let search_stream = self
712            .ldap
713            .streaming_search_with(adapters, base, scope, filter.filter().as_str(), attributes)
714            .await;
715
716        if let Err(error) = search_stream {
717            return Err(Error::Query(
718                format!("Error searching for record: {:?}", error),
719                error,
720            ));
721        }
722
723        let search_stream = search_stream.unwrap();
724
725        let stream = Stream::new(search_stream);
726        Ok(stream)
727    }
728
729    ///
730    /// Create a new record in the LDAP server. The record will be created in the provided base DN.
731    ///
732    /// # Arguments
733    ///
734    /// * `uid` - The uid of the record
735    /// * `base` - The base DN to create the record
736    /// * `data` - The attributes of the record
737    ///
738    ///
739    /// # Returns
740    ///
741    /// * `Result<(), Error>` - Returns an error if the record creation fails
742    ///
743    ///
744    /// # Example
745    ///
746    /// ```no_run
747    /// use simple_ldap::{LdapClient, LdapConfig};
748    /// use url::Url;
749    /// use std::collections::HashSet;
750    ///
751    /// #[tokio::main]
752    /// async fn main(){
753    ///     let ldap_config = LdapConfig {
754    ///         bind_dn: String::from("cn=manager"),
755    ///         bind_password: String::from("password"),
756    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
757    ///         dn_attribute: None,
758    ///         connection_settings: None
759    ///     };
760    ///
761    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
762    ///
763    ///     let data = vec![
764    ///         ( "objectClass",HashSet::from(["organizationalPerson", "inetorgperson", "top", "person"]),),
765    ///         ("uid",HashSet::from(["bd9b91ec-7a69-4166-bf67-cc7e553b2fd9"]),),
766    ///         ("cn", HashSet::from(["Kasun"])),
767    ///         ("sn", HashSet::from(["Ranasingh"])),
768    ///     ];
769    ///
770    ///     let result = client.create("bd9b91ec-7a69-4166-bf67-cc7e553b2fd9", "ou=people,dc=example,dc=com", data).await;
771    /// }
772    /// ```
773    ///
774    pub async fn create(
775        &mut self,
776        uid: &str,
777        base: &str,
778        data: Vec<(&str, HashSet<&str>)>,
779    ) -> Result<(), Error> {
780        let dn = format!("uid={},{}", uid, base);
781        let save = self.ldap.add(dn.as_str(), data).await;
782        if let Err(err) = save {
783            return Err(Error::Create(
784                format!("Error saving record: {:?}", err),
785                err,
786            ));
787        }
788        let save = save.unwrap().success();
789
790        if let Err(err) = save {
791            return Err(Error::Create(
792                format!("Error saving record: {:?}", err),
793                err,
794            ));
795        }
796        let res = save.unwrap();
797        debug!("Sucessfully created record result: {:?}", res);
798        Ok(())
799    }
800
801    ///
802    /// Update a record in the LDAP server. The record will be updated in the provided base DN.
803    ///
804    /// # Arguments
805    ///
806    /// * `uid` - The uid of the record
807    /// * `base` - The base DN to update the record
808    /// * `data` - The attributes of the record
809    /// * `new_uid` - The new uid of the record. If the new uid is provided, the uid of the record will be updated.
810    ///
811    ///
812    /// # Returns
813    ///
814    /// * `Result<(), Error>` - Returns an error if the record update fails
815    ///
816    ///
817    /// # Example
818    ///
819    /// ```no_run
820    /// use simple_ldap::{
821    ///     LdapClient, LdapConfig,
822    ///     ldap3::Mod
823    /// };
824    /// use url::Url;
825    /// use std::collections::HashSet;
826    ///
827    /// #[tokio::main]
828    /// async fn main(){
829    ///     let ldap_config = LdapConfig {
830    ///         bind_dn: String::from("cn=manager"),
831    ///         bind_password: String::from("password"),
832    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
833    ///         dn_attribute: None,
834    ///         connection_settings: None
835    ///     };
836    ///
837    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
838    ///
839    ///     let data = vec![
840    ///         Mod::Replace("cn", HashSet::from(["Jhon_Update"])),
841    ///         Mod::Replace("sn", HashSet::from(["Eliet_Update"])),
842    ///     ];
843    ///
844    ///     let result = client.update(
845    ///         "e219fbc0-6df5-4bc3-a6ee-986843bb157e",
846    ///         "ou=people,dc=example,dc=com",
847    ///         data,
848    ///         None
849    ///     ).await;
850    /// }
851    /// ```
852    ///
853    pub async fn update(
854        &mut self,
855        uid: &str,
856        base: &str,
857        data: Vec<Mod<&str>>,
858        new_uid: Option<&str>,
859    ) -> Result<(), Error> {
860        let dn = format!("uid={},{}", uid, base);
861
862        let res = self.ldap.modify(dn.as_str(), data).await;
863        if let Err(err) = res {
864            return Err(Error::Update(
865                format!("Error updating record: {:?}", err),
866                err,
867            ));
868        }
869
870        let res = res.unwrap().success();
871        if let Err(err) = res {
872            match err {
873                LdapError::LdapResult { result } => {
874                    if result.rc == NO_SUCH_RECORD {
875                        return Err(Error::NotFound(format!(
876                            "No records found for the uid: {:?}",
877                            uid
878                        )));
879                    }
880                }
881                _ => {
882                    return Err(Error::Update(
883                        format!("Error updating record: {:?}", err),
884                        err,
885                    ));
886                }
887            }
888        }
889
890        if new_uid.is_none() {
891            return Ok(());
892        }
893
894        let new_uid = new_uid.unwrap();
895        if !uid.eq_ignore_ascii_case(new_uid) {
896            let new_dn = format!("uid={}", new_uid);
897            let dn_update = self
898                .ldap
899                .modifydn(dn.as_str(), new_dn.as_str(), true, None)
900                .await;
901            if let Err(err) = dn_update {
902                error!("Failed to update dn for record {:?} error {:?}", uid, err);
903                return Err(Error::Update(
904                    format!("Failed to update dn for record {:?}", uid),
905                    err,
906                ));
907            }
908
909            let dn_update = dn_update.unwrap().success();
910            if let Err(err) = dn_update {
911                error!("Failed to update dn for record {:?} error {:?}", uid, err);
912                return Err(Error::Update(
913                    format!("Failed to update dn for record {:?}", uid),
914                    err,
915                ));
916            }
917
918            let res = dn_update.unwrap();
919            debug!("Sucessfully updated dn result: {:?}", res);
920        }
921
922        Ok(())
923    }
924
925    ///
926    /// Delete a record in the LDAP server. The record will be deleted in the provided base DN.
927    ///
928    /// # Arguments
929    ///
930    /// * `uid` - The uid of the record
931    /// * `base` - The base DN to delete the record
932    ///
933    ///
934    /// # Returns
935    ///
936    /// * `Result<(), Error>` - Returns an error if the record delete fails
937    ///
938    ///
939    /// # Example
940    ///
941    /// ```no_run
942    /// use simple_ldap::{LdapClient, LdapConfig};
943    /// use url::Url;
944    ///
945    /// #[tokio::main]
946    /// async fn main(){
947    ///     let ldap_config = LdapConfig {
948    ///         bind_dn: String::from("cn=manager"),
949    ///         bind_password: String::from("password"),
950    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
951    ///         dn_attribute: None,
952    ///         connection_settings: None
953    ///     };
954    ///
955    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
956    ///
957    ///     let result = client.delete("e219fbc0-6df5-4bc3-a6ee-986843bb157e", "ou=people,dc=example,dc=com").await;
958    /// }
959    /// ```
960    pub async fn delete(&mut self, uid: &str, base: &str) -> Result<(), Error> {
961        let dn = format!("uid={},{}", uid, base);
962        let delete = self.ldap.delete(dn.as_str()).await;
963
964        if let Err(err) = delete {
965            return Err(Error::Delete(
966                format!("Error deleting record: {:?}", err),
967                err,
968            ));
969        }
970        let delete = delete.unwrap().success();
971        if let Err(err) = delete {
972            match err {
973                LdapError::LdapResult { result } => {
974                    if result.rc == NO_SUCH_RECORD {
975                        return Err(Error::NotFound(format!(
976                            "No records found for the uid: {:?}",
977                            uid
978                        )));
979                    }
980                }
981                _ => {
982                    return Err(Error::Delete(
983                        format!("Error deleting record: {:?}", err),
984                        err,
985                    ));
986                }
987            }
988        }
989        debug!("Sucessfully deleted record result: {:?}", uid);
990        Ok(())
991    }
992
993    ///
994    /// Create a new group in the LDAP server. The group will be created in the provided base DN.
995    ///
996    /// # Arguments
997    ///
998    /// * `group_name` - The name of the group
999    /// * `group_ou` - The ou of the group
1000    /// * `description` - The description of the group
1001    ///
1002    /// # Returns
1003    ///
1004    /// * `Result<(), Error>` - Returns an error if the group creation fails
1005    ///
1006    ///
1007    /// # Example
1008    ///
1009    /// ```no_run
1010    /// use simple_ldap::{LdapClient, LdapConfig};
1011    /// use url::Url;
1012    ///
1013    /// #[tokio::main]
1014    /// async fn main(){
1015    ///     let ldap_config = LdapConfig {
1016    ///         bind_dn: String::from("cn=manager"),
1017    ///         bind_password: String::from("password"),
1018    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1019    ///         dn_attribute: None,
1020    ///         connection_settings: None
1021    ///     };
1022    ///
1023    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
1024    ///
1025    ///     let result = client.create_group("test_group", "ou=groups,dc=example,dc=com", "test group").await;
1026    /// }
1027    /// ```
1028    pub async fn create_group(
1029        &mut self,
1030        group_name: &str,
1031        group_ou: &str,
1032        description: &str,
1033    ) -> Result<(), Error> {
1034        let dn = format!("cn={},{}", group_name, group_ou);
1035
1036        let data = vec![
1037            ("objectClass", HashSet::from(["top", "groupOfNames"])),
1038            ("cn", HashSet::from([group_name])),
1039            ("ou", HashSet::from([group_ou])),
1040            ("description", HashSet::from([description])),
1041        ];
1042        let save = self.ldap.add(dn.as_str(), data).await;
1043        if let Err(err) = save {
1044            return Err(Error::Create(
1045                format!("Error saving record: {:?}", err),
1046                err,
1047            ));
1048        }
1049        let save = save.unwrap().success();
1050
1051        if let Err(err) = save {
1052            return Err(Error::Create(
1053                format!("Error creating group: {:?}", err),
1054                err,
1055            ));
1056        }
1057        let res = save.unwrap();
1058        debug!("Sucessfully created group result: {:?}", res);
1059        Ok(())
1060    }
1061
1062    ///
1063    /// Add users to a group in the LDAP server. The group will be updated in the provided base DN.
1064    ///
1065    /// # Arguments
1066    ///
1067    /// * `users` - The list of users to add to the group
1068    /// * `group_dn` - The dn of the group
1069    ///
1070    ///
1071    /// # Returns
1072    ///
1073    /// * `Result<(), Error>` - Returns an error if failed to add users to the group
1074    ///
1075    ///
1076    /// # Example
1077    ///
1078    /// ```no_run
1079    /// use simple_ldap::{LdapClient, LdapConfig};
1080    /// use url::Url;
1081    ///
1082    /// #[tokio::main]
1083    /// async fn main(){
1084    ///     let ldap_config = LdapConfig {
1085    ///         bind_dn: String::from("cn=manager"),
1086    ///         bind_password: String::from("password"),
1087    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1088    ///         dn_attribute: None,
1089    ///         connection_settings: None
1090    ///     };
1091    ///
1092    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
1093    ///
1094    ///     let result = client.add_users_to_group(
1095    ///         vec!["uid=bd9b91ec-7a69-4166-bf67-cc7e553b2fd9,ou=people,dc=example,dc=com"],
1096    ///         "cn=test_group,ou=groups,dc=example,dc=com").await;
1097    /// }
1098    /// ```
1099    pub async fn add_users_to_group(
1100        &mut self,
1101        users: Vec<&str>,
1102        group_dn: &str,
1103    ) -> Result<(), Error> {
1104        let mut mods = Vec::new();
1105        let users = users.iter().copied().collect::<HashSet<&str>>();
1106        mods.push(Mod::Replace("member", users));
1107        let res = self.ldap.modify(group_dn, mods).await;
1108        if let Err(err) = res {
1109            return Err(Error::Update(
1110                format!("Error updating record: {:?}", err),
1111                err,
1112            ));
1113        }
1114
1115        let res = res.unwrap().success();
1116        if let Err(err) = res {
1117            match err {
1118                LdapError::LdapResult { result } => {
1119                    if result.rc == NO_SUCH_RECORD {
1120                        return Err(Error::NotFound(format!(
1121                            "No records found for the uid: {:?}",
1122                            group_dn
1123                        )));
1124                    }
1125                }
1126                _ => {
1127                    return Err(Error::Update(
1128                        format!("Error updating record: {:?}", err),
1129                        err,
1130                    ));
1131                }
1132            }
1133        }
1134        Ok(())
1135    }
1136
1137    ///
1138    /// Get users of a group in the LDAP server. The group will be searched in the provided base DN.
1139    ///
1140    /// # Arguments
1141    ///
1142    /// * `group_dn` - The dn of the group
1143    /// * `base_dn` - The base dn to search for the users
1144    /// * `scope` - The scope of the search
1145    /// * `attributes` - The attributes to return from the search
1146    ///
1147    ///
1148    /// # Returns
1149    ///
1150    /// * `Result<Vec<T>, Error>` - Returns a vector of structs of type T
1151    ///
1152    ///
1153    /// # Example
1154    ///
1155    /// ```no_run
1156    /// use simple_ldap::{
1157    ///     LdapClient, LdapConfig,
1158    ///     ldap3::Scope
1159    /// };
1160    /// use url::Url;
1161    /// use serde::Deserialize;
1162    ///
1163    /// #[derive(Debug, Deserialize)]
1164    /// struct User {
1165    ///     uid: String,
1166    ///     cn: String,
1167    ///     sn: String,
1168    /// }
1169    ///
1170    /// #[tokio::main]
1171    /// async fn main(){
1172    ///     let ldap_config = LdapConfig {
1173    ///         bind_dn: String::from("cn=manager"),
1174    ///         bind_password: String::from("password"),
1175    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1176    ///         dn_attribute: None,
1177    ///         connection_settings: None
1178    ///     };
1179    ///
1180    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
1181    ///
1182    ///     let result = client.get_members::<User>(
1183    ///         "cn=test_group,ou=groups,dc=example,dc=com",
1184    ///         "ou=people,dc=example,dc=com",
1185    ///         Scope::OneLevel,
1186    ///         &vec!["cn", "sn", "uid"]
1187    ///     ).await;
1188    /// }
1189    /// ```
1190    ///
1191    pub async fn get_members<T: for<'a> serde::Deserialize<'a>>(
1192        &mut self,
1193        group_dn: &str,
1194        base_dn: &str,
1195        scope: Scope,
1196        attributes: &Vec<&str>,
1197    ) -> Result<Vec<T>, Error> {
1198        let search = self
1199            .ldap
1200            .search(
1201                group_dn,
1202                Scope::Base,
1203                "(objectClass=groupOfNames)",
1204                vec!["member"],
1205            )
1206            .await;
1207
1208        if let Err(error) = search {
1209            return Err(Error::Query(
1210                format!("Error searching for record: {:?}", error),
1211                error,
1212            ));
1213        }
1214        let result = search.unwrap().success();
1215        if let Err(error) = result {
1216            return Err(Error::Query(
1217                format!("Error searching for record: {:?}", error),
1218                error,
1219            ));
1220        }
1221
1222        let records = result.unwrap().0;
1223
1224        if records.len() > 1 {
1225            return Err(Error::MultipleResults(String::from(
1226                "Found multiple records for the search criteria",
1227            )));
1228        }
1229
1230        if records.is_empty() {
1231            return Err(Error::NotFound(String::from(
1232                "No records found for the search criteria",
1233            )));
1234        }
1235
1236        let record = records.first().unwrap();
1237
1238        let x = SearchEntry::construct(record.to_owned());
1239        let result: HashMap<&str, Vec<String>> = x
1240            .attrs
1241            .iter()
1242            .filter(|(_, value)| !value.is_empty())
1243            .map(|(arrta, value)| (arrta.as_str(), value.to_owned()))
1244            .collect();
1245
1246        let mut members = Vec::new();
1247        for member in result.get("member").unwrap() {
1248            let uid = member.split(',').collect::<Vec<&str>>()[0]
1249                .split('=')
1250                .collect::<Vec<&str>>();
1251            let filter = EqFilter::from(uid[0].to_string(), uid[1].to_string());
1252            let x = self.search::<T>(base_dn, scope, &filter, attributes).await;
1253            match x {
1254                Ok(x) => {
1255                    members.push(x);
1256                }
1257                Err(err) => {
1258                    error!("Error getting member {:?} error {:?}", member, err);
1259                }
1260            }
1261        }
1262
1263        Ok(members)
1264    }
1265
1266    ///
1267    /// Remove users from a group in the LDAP server. The group will be updated in the provided base DN.
1268    /// This method will remove all the users provided from the group.
1269    ///
1270    ///
1271    /// # Arguments
1272    ///
1273    /// * `group_dn` - The dn of the group
1274    /// * `users` - The list of users to remove from the group
1275    ///
1276    ///
1277    /// # Returns
1278    ///
1279    /// * `Result<(), Error>` - Returns an error if failed to remove users from the group
1280    ///
1281    ///
1282    /// # Example
1283    ///
1284    /// ```no_run
1285    /// use simple_ldap::{LdapClient, LdapConfig};
1286    /// use url::Url;
1287    /// use std::collections::HashSet;
1288    ///
1289    /// #[tokio::main]
1290    /// async fn main(){
1291    ///     let ldap_config = LdapConfig {
1292    ///         bind_dn: String::from("cn=manager"),
1293    ///         bind_password: String::from("password"),
1294    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1295    ///         dn_attribute: None,
1296    ///         connection_settings: None
1297    ///     };
1298    ///
1299    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
1300    ///
1301    ///     let result = client.remove_users_from_group("cn=test_group,ou=groups,dc=example,dc=com",
1302    ///     vec!["uid=bd9b91ec-7a69-4166-bf67-cc7e553b2fd9,ou=people,dc=example,dc=com"]).await;
1303    /// }
1304    /// ```
1305    pub async fn remove_users_from_group(
1306        &mut self,
1307        group_dn: &str,
1308        users: Vec<&str>,
1309    ) -> Result<(), Error> {
1310        let mut mods = Vec::new();
1311        let users = users.iter().copied().collect::<HashSet<&str>>();
1312        mods.push(Mod::Delete("member", users));
1313        let res = self.ldap.modify(group_dn, mods).await;
1314        if let Err(err) = res {
1315            return Err(Error::Update(
1316                format!("Error removing users from group:{:?}: {:?}", group_dn, err),
1317                err,
1318            ));
1319        }
1320
1321        let res = res.unwrap().success();
1322        if let Err(err) = res {
1323            match err {
1324                LdapError::LdapResult { result } => {
1325                    if result.rc == NO_SUCH_RECORD {
1326                        return Err(Error::NotFound(format!(
1327                            "No records found for the uid: {:?}",
1328                            group_dn
1329                        )));
1330                    }
1331                }
1332                _ => {
1333                    return Err(Error::Update(
1334                        format!("Error removing users from group:{:?}: {:?}", group_dn, err),
1335                        err,
1336                    ));
1337                }
1338            }
1339        }
1340        Ok(())
1341    }
1342
1343    ///
1344    /// Get the groups associated with a user in the LDAP server. The user will be searched in the provided base DN.
1345    ///
1346    /// # Arguments
1347    ///
1348    /// * `group_ou` - The ou to search for the groups
1349    /// * `user_dn` - The dn of the user
1350    ///
1351    /// # Returns
1352    ///
1353    /// * `Result<Vec<String>, Error>` - Returns a vector of group names
1354    ///
1355    ///
1356    /// # Example
1357    ///
1358    /// ```no_run
1359    /// use simple_ldap::{LdapClient, LdapConfig};
1360    /// use url::Url;
1361    ///
1362    /// #[tokio::main]
1363    /// async fn main(){
1364    ///     let ldap_config = LdapConfig {
1365    ///         bind_dn: String::from("cn=manager"),
1366    ///         bind_password: String::from("password"),
1367    ///         ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1368    ///         dn_attribute: None,
1369    ///         connection_settings: None
1370    ///     };
1371    ///
1372    ///     let mut client = LdapClient::new(ldap_config).await.unwrap();
1373    ///
1374    ///     let result = client.get_associtated_groups("ou=groups,dc=example,dc=com",
1375    ///     "uid=bd9b91ec-7a69-4166-bf67-cc7e553b2fd9,ou=people,dc=example,dc=com").await;
1376    /// }
1377    /// ```
1378    pub async fn get_associtated_groups(
1379        &mut self,
1380        group_ou: &str,
1381        user_dn: &str,
1382    ) -> Result<Vec<String>, Error> {
1383        let group_filter = Box::new(EqFilter::from(
1384            "objectClass".to_string(),
1385            "groupOfNames".to_string(),
1386        ));
1387
1388        let user_filter = Box::new(EqFilter::from("member".to_string(), user_dn.to_string()));
1389        let mut filter = AndFilter::default();
1390        filter.add(group_filter);
1391        filter.add(user_filter);
1392
1393        let search = self
1394            .ldap
1395            .search(
1396                group_ou,
1397                Scope::Subtree,
1398                filter.filter().as_str(),
1399                vec!["cn"],
1400            )
1401            .await;
1402
1403        if let Err(error) = search {
1404            return Err(Error::Query(
1405                format!("Error searching for record: {:?}", error),
1406                error,
1407            ));
1408        }
1409        let result = search.unwrap().success();
1410        if let Err(error) = result {
1411            return Err(Error::Query(
1412                format!("Error searching for record: {:?}", error),
1413                error,
1414            ));
1415        }
1416
1417        let records = result.unwrap().0;
1418
1419        if records.is_empty() {
1420            return Err(Error::NotFound(String::from(
1421                "User does not belong to any groups",
1422            )));
1423        }
1424
1425        let record = records
1426            .iter()
1427            .map(|record| SearchEntry::construct(record.to_owned()))
1428            .map(|se| se.attrs)
1429            .flat_map(|att| {
1430                att.get("cn")
1431                    .unwrap()
1432                    .iter()
1433                    .map(|x| x.to_owned())
1434                    .collect::<Vec<String>>()
1435            })
1436            .collect::<Vec<String>>();
1437
1438        Ok(record)
1439    }
1440}
1441
1442fn to_signle_value<T: for<'a> Deserialize<'a>>(search_entry: SearchEntry) -> Result<T, Error> {
1443    let result: HashMap<&str, serde_value::Value> = search_entry
1444        .attrs
1445        .iter()
1446        .filter(|(_, value)| !value.is_empty())
1447        .map(|(arrta, value)| (arrta.as_str(), map_to_single_value(value.first())))
1448        .collect();
1449
1450    let value = serde_value::Value::Map(
1451        result
1452            .into_iter()
1453            .map(|(k, v)| (serde_value::Value::String(k.to_string()), v))
1454            .collect(),
1455    );
1456
1457    Ok(T::deserialize(value).map_err(|err| {
1458        Error::Mapping(format!(
1459            "Error converting search result to object, {:?}",
1460            err
1461        ))
1462    })?)
1463}
1464
1465fn to_multi_value<T: for<'a> Deserialize<'a>>(search_entry: SearchEntry) -> Result<T, Error> {
1466    let result: HashMap<&str, serde_value::Value> = search_entry
1467        .attrs
1468        .iter()
1469        .filter(|(_, value)| !value.is_empty())
1470        .map(|(arrta, value)| (arrta.as_str(), map_to_multi_value(value)))
1471        .collect();
1472
1473    let value = serde_value::Value::Map(
1474        result
1475            .into_iter()
1476            .map(|(k, v)| (serde_value::Value::String(k.to_string()), v))
1477            .collect(),
1478    );
1479
1480    Ok(T::deserialize(value).map_err(|err| {
1481        Error::Mapping(format!(
1482            "Error converting search result to object, {:?}",
1483            err
1484        ))
1485    })?)
1486}
1487
1488fn map_to_single_value(attra_value: Option<&String>) -> serde_value::Value {
1489    match attra_value {
1490        Some(value) => serde_value::Value::String(value.to_string()),
1491        None => serde_value::Value::Option(Option::None),
1492    }
1493}
1494
1495fn map_to_multi_value(attra_value: &Vec<String>) -> serde_value::Value {
1496    serde_value::Value::Seq(
1497        attra_value
1498            .iter()
1499            .map(|value| serde_value::Value::String(value.to_string()))
1500            .collect(),
1501    )
1502}
1503
1504/// The Stream struct is used to iterate through the search results.
1505/// The stream will return a Record object. The Record object can be used to map the search result to a struct.
1506/// After the stream is finished, the cleanup method should be called to cleanup the stream.
1507///
1508pub struct Stream<'a, S, A> {
1509    search_stream: SearchStream<'a, S, A>,
1510}
1511
1512impl<'a, S, A> Stream<'a, S, A>
1513where
1514    S: AsRef<str> + Send + Sync + 'a,
1515    A: AsRef<[S]> + Send + Sync + 'a,
1516{
1517    fn new(search_stream: SearchStream<'a, S, A>) -> Stream<'a, S, A> {
1518        Stream { search_stream }
1519    }
1520
1521    async fn next_inner(&mut self) -> Result<StreamResult<SearchEntry>, Error> {
1522        let next = self.search_stream.next().await;
1523        if let Err(err) = next {
1524            return Err(Error::Query(
1525                format!("Error getting next record: {:?}", err),
1526                err,
1527            ));
1528        }
1529
1530        if self.search_stream.state() != StreamState::Active {
1531            // self.limit = self.count; // Set the limit to the count, to that poll_next will return None
1532            return Ok(StreamResult::Finished);
1533        }
1534
1535        let entry = next.unwrap();
1536        match entry {
1537            Some(entry) => {
1538                // self.count += 1;
1539                let entry = SearchEntry::construct(entry);
1540                return Ok(StreamResult::Record(entry));
1541            }
1542            None => {
1543                // self.limit = self.count; // Set the limit to the count, to that poll_next will return None
1544                return Ok(StreamResult::Finished);
1545            }
1546        }
1547    }
1548
1549    ///
1550    /// Cleanup the stream. This method should be called after the stream is finished,
1551    /// especially if you're stopping before the stream ends naturally.
1552    ///
1553    /// This method will cleanup the stream and close the connection.
1554    ///
1555    pub async fn cleanup(&mut self) -> Result<(), Error> {
1556        let state = self.search_stream.state();
1557        if state == StreamState::Done || state == StreamState::Closed {
1558            return Ok(());
1559        }
1560        let _res = self.search_stream.finish().await;
1561        let msgid = self.search_stream.ldap_handle().last_id();
1562        let result = self.search_stream.ldap_handle().abandon(msgid).await;
1563
1564        match result {
1565            Ok(_) => {
1566                debug!("Sucessfully abandoned search result: {:?}", msgid);
1567                Ok(())
1568            }
1569            Err(err) => {
1570                error!("Error abandoning search result: {:?}", err);
1571                Err(Error::Abandon(
1572                    format!("Error abandoning search result: {:?}", err),
1573                    err,
1574                ))
1575            }
1576        }
1577    }
1578}
1579
1580impl<'a, S, A> futures::stream::Stream for Stream<'a, S, A>
1581where
1582    S: AsRef<str> + Send + Sync + 'a,
1583    A: AsRef<[S]> + Send + Sync + 'a,
1584{
1585    type Item = Result<Record, Error>;
1586
1587    fn poll_next(
1588        mut self: Pin<&mut Self>,
1589        cx: &mut Context<'_>,
1590    ) -> Poll<Option<Result<Record, Error>>> {
1591        let poll = self.next_inner().boxed().as_mut().poll(cx);
1592        match poll {
1593            Poll::Ready(result) => match result {
1594                Ok(result) => match result {
1595                    StreamResult::Record(record) => Poll::Ready(Some(Ok(Record {
1596                        search_entry: record,
1597                    }))),
1598                    StreamResult::Done => Poll::Ready(None),
1599                    StreamResult::Finished => Poll::Ready(None),
1600                },
1601                Err(er) => {
1602                    return Poll::Ready(Some(Err(er)));
1603                }
1604            },
1605            Poll::Pending => Poll::Pending,
1606        }
1607    }
1608}
1609
1610/// The Record struct is used to map the search result to a struct.
1611/// The Record struct has a method to_record which will map the search result to a struct.
1612/// The Record struct has a method to_multi_valued_record which will map the search result to a struct with multi valued attributes.
1613pub struct Record {
1614    search_entry: SearchEntry,
1615}
1616
1617impl Record {
1618    ///
1619    /// Create a new Record object with single valued attributes.
1620    /// This is essentially parsing the response records into usable types.
1621    //
1622    // This is kind of missnomer, as we aren't creating records here.
1623    // Perhaps something like "deserialize" would fit better?
1624    pub fn to_record<T: for<'b> serde::Deserialize<'b>>(self) -> Result<T, Error> {
1625        to_signle_value(self.search_entry)
1626    }
1627
1628    pub fn to_multi_valued_record_<T: for<'b> serde::Deserialize<'b>>(self) -> Result<T, Error> {
1629        to_multi_value(self.search_entry)
1630    }
1631}
1632
1633pub enum StreamResult<T> {
1634    Record(T),
1635    Done,
1636    Finished,
1637}
1638
1639///
1640/// The error type for the LDAP client
1641///
1642#[derive(Debug, Error)]
1643pub enum Error {
1644    /// Error occured when performing a LDAP query
1645    #[error("{0}")]
1646    Query(String, #[source] LdapError),
1647    /// No records found for the search criteria
1648    #[error("{0}")]
1649    NotFound(String),
1650    /// Multiple records found for the search criteria
1651    #[error("{0}")]
1652    MultipleResults(String),
1653    /// Authenticating a user failed.
1654    #[error("{0}")]
1655    AuthenticationFailed(String),
1656    /// Error occured when creating a record
1657    #[error("{0}")]
1658    Create(String, #[source] LdapError),
1659    /// Error occured when updating a record
1660    #[error("{0}")]
1661    Update(String, #[source] LdapError),
1662    /// Error occured when deleting a record
1663    #[error("{0}")]
1664    Delete(String, #[source] LdapError),
1665    /// Error occured when mapping the search result to a struct
1666    #[error("{0}")]
1667    Mapping(String),
1668    /// Error occurred while attempting to create an LDAP connection
1669    #[error("{0}")]
1670    Connection(String, #[source] LdapError),
1671    /// Error occurred while attempting to close an LDAP connection.
1672    /// Includes unbind issues.
1673    #[error("{0}")]
1674    Close(String, #[source] LdapError),
1675    /// Error occurred while abandoning the search result
1676    #[error("{0}")]
1677    Abandon(String, #[source] LdapError),
1678}
1679
1680#[cfg(test)]
1681mod tests {
1682    //! Local tests that don't need to connect to a server.
1683
1684    use super::*;
1685    use serde::Deserialize;
1686
1687    #[test]
1688    fn create_json_multi_value_test() {
1689        let mut map: HashMap<String, Vec<String>> = HashMap::new();
1690        map.insert(
1691            "key1".to_string(),
1692            vec!["value1".to_string(), "value2".to_string()],
1693        );
1694        map.insert(
1695            "key2".to_string(),
1696            vec!["value3".to_string(), "value4".to_string()],
1697        );
1698        let entry = SearchEntry {
1699            dn: "dn".to_string(),
1700            attrs: map,
1701            bin_attrs: HashMap::new(),
1702        };
1703
1704        let test = to_multi_value::<TestMultiValued>(entry);
1705        assert!(test.is_ok());
1706        let test = test.unwrap();
1707        assert_eq!(test.key1, vec!["value1".to_string(), "value2".to_string()]);
1708        assert_eq!(test.key2, vec!["value3".to_string(), "value4".to_string()]);
1709    }
1710
1711    #[test]
1712    fn create_json_single_value_test() {
1713        let mut map: HashMap<String, Vec<String>> = HashMap::new();
1714        map.insert("key1".to_string(), vec!["value1".to_string()]);
1715        map.insert("key2".to_string(), vec!["value2".to_string()]);
1716        map.insert("key4".to_string(), vec!["value4".to_string()]);
1717        let entry = SearchEntry {
1718            dn: "dn".to_string(),
1719            attrs: map,
1720            bin_attrs: HashMap::new(),
1721        };
1722
1723        let test = to_signle_value::<TestSingleValued>(entry);
1724        assert!(test.is_ok());
1725        let test = test.unwrap();
1726        assert_eq!(test.key1, "value1".to_string());
1727        assert_eq!(test.key2, "value2".to_string());
1728        assert!(test.key3.is_none());
1729        assert_eq!(test.key4.unwrap(), "value4".to_string());
1730    }
1731
1732    #[derive(Debug, Deserialize)]
1733    struct TestMultiValued {
1734        key1: Vec<String>,
1735        key2: Vec<String>,
1736    }
1737
1738    #[derive(Debug, Deserialize)]
1739    struct TestSingleValued {
1740        key1: String,
1741        key2: String,
1742        key3: Option<String>,
1743        key4: Option<String>,
1744    }
1745}
1746
1747// Add readme examples to doctests:
1748// https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html#include-items-only-when-collecting-doctests
1749#[doc = include_str!("../README.md")]
1750#[cfg(doctest)]
1751pub struct ReadmeDoctests;