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