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