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