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