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