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
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,
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//! uid: String,
43//! cn: String,
44//! sn: String,
45//! }
46//!
47//!
48//! #[tokio::main]
49//! async fn main(){
50//! let ldap_config = LdapConfig {
51//! bind_dn: String::from("cn=manager"),
52//! bind_password: String::from("password"),
53//! ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
54//! dn_attribute: None,
55//! connection_settings: None
56//! };
57//! let mut client = LdapClient::new(ldap_config).await.unwrap();
58//! let name_filter = EqFilter::from("cn".to_string(), "Sam".to_string());
59//! let user: User = client
60//! .search::<User>(
61//! "ou=people,dc=example,dc=com",
62//! Scope::OneLevel,
63//! &name_filter,
64//! &vec!["cn", "sn", "uid"],
65//! ).await.unwrap();
66//! }
67//! ```
68//!
69//!
70//! ## Compile time features
71//!
72//! * `tls-native` - (Enabled by default) Enables TLS support using the systems native implementation.
73//! * `tls-rustls` - Enables TLS support using `rustls`. **Conflicts with `tls-native` so you need to disable default features to use this.
74//! * `pool` - Enable connection pooling
75//!
76
77use std::{
78 collections::{HashMap, HashSet},
79 pin::Pin,
80 task::{Context, Poll},
81};
82
83use filter::{AndFilter, EqFilter, Filter};
84use futures::FutureExt;
85use ldap3::{
86 adapters::{Adapter, EntriesOnly, PagedResults},
87 Ldap, LdapConnAsync, LdapConnSettings, LdapError, Mod, Scope, SearchEntry, SearchStream,
88 StreamState,
89};
90use serde::Deserialize;
91use thiserror::Error;
92use tracing::{debug, error};
93use url::Url;
94
95pub mod filter;
96#[cfg(feature = "pool")]
97pub mod pool;
98pub extern crate ldap3;
99
100const LDAP_ENTRY_DN: &str = "entryDN";
101const NO_SUCH_RECORD: u32 = 32;
102
103
104/// Configuration and authentication for LDAP connection
105#[derive(derive_more::Debug, Clone)]
106pub struct LdapConfig {
107 pub ldap_url: Url,
108 /// DistinguishedName, aka the "username" to use for the connection.
109 pub bind_dn: String,
110 #[debug(skip)] // We don't want to print passwords.
111 pub bind_password: String,
112 pub dn_attribute: Option<String>,
113 /// Low level configuration for the connection.
114 /// You can probably skip it.
115 #[debug(skip)] // Debug omitted, because it just doesn't implement it.
116 pub connection_settings: Option<LdapConnSettings>,
117}
118
119
120///
121/// High-level LDAP client wrapper ontop of ldap3 crate. This wrapper provides a high-level interface to perform LDAP operations
122/// including authentication, search, update, delete
123///
124#[derive(Debug, Clone)]
125pub struct LdapClient {
126 /// The internal connection handle.
127 ldap: Ldap,
128 dn_attr: Option<String>,
129}
130
131impl LdapClient {
132 ///
133 /// Creates a new asynchronous LDAP client.s
134 /// It's capable of running multiple operations concurrently.
135 ///
136 /// # Bind
137 ///
138 /// This performs a bind on the connection so need to worry about that.
139 ///
140 pub async fn new(config: LdapConfig) -> Result<Self, Error> {
141 debug!("Creating new connection");
142
143 // With or without connection settings
144 let (conn, mut ldap) = match config.connection_settings {
145 None => LdapConnAsync::from_url(&config.ldap_url).await,
146 Some(settings) => {
147 LdapConnAsync::from_url_with_settings(settings, &config.ldap_url).await
148 }
149 }
150 .map_err(|ldap_err| {
151 Error::Connection(
152 String::from("Failed to initialize LDAP connection."),
153 ldap_err,
154 )
155 })?;
156
157 ldap3::drive!(conn);
158
159 ldap.simple_bind(&config.bind_dn, &config.bind_password)
160 .await
161 .map_err(|ldap_err| Error::Connection(String::from("Bind failed"), ldap_err))?
162 .success()
163 .map_err(|ldap_err| Error::Connection(String::from("Bind failed"), ldap_err))?;
164
165 Ok(Self {
166 dn_attr: config.dn_attribute,
167 ldap,
168 })
169 }
170}
171
172impl LdapClient {
173 /// Returns the ldap3 client
174 #[deprecated = "This abstraction leakage will be removed in a future release.
175 Use the provided methods instead. If something's missing, open an issue in github."]
176 pub fn get_inner(&self) -> Ldap {
177 self.ldap.clone()
178 }
179
180 /// End the LDAP connection.
181 ///
182 /// **Caution advised!**
183 ///
184 /// This will close the connection for all clones of this client as well,
185 /// including open streams. So make sure that you're really good to close.
186 ///
187 /// Closing an LDAP connection with an unbind is *a curtesy.*
188 /// It's fine to skip it, and because of the async hurdless outlined above,
189 /// I would perhaps even recommend it.
190 // Consuming self to prevent accidental use after unbind.
191 // This also conveniently prevents calling this with pooled clients, as the
192 // wrapper `Object` prohibiths moving.
193 pub async fn unbind(mut self) -> Result<(), Error> {
194 match self.ldap.unbind().await {
195 Ok(_) => Ok(()),
196 Err(error) => Err(Error::Close(String::from("Failed to unbind"), error)),
197 }
198 }
199
200 ///
201 /// The user is authenticated by searching for the user in the LDAP server.
202 /// The search is performed using the provided filter. The filter should be a filter that matches a single user.
203 ///
204 /// # Arguments
205 ///
206 /// * `base` - The base DN to search for the user
207 /// * `uid` - The uid of the user
208 /// * `password` - The password of the user
209 /// * `filter` - The filter to search for the user
210 ///
211 ///
212 /// # Returns
213 ///
214 /// * `Result<(), Error>` - Returns an error if the authentication fails
215 ///
216 ///
217 /// # Example
218 ///
219 /// ```no_run
220 /// use simple_ldap::{
221 /// LdapClient, LdapConfig,
222 /// filter::EqFilter
223 /// };
224 /// use url::Url;
225 ///
226 /// #[tokio::main]
227 /// async fn main(){
228 /// let ldap_config = LdapConfig {
229 /// bind_dn: String::from("cn=manager"),
230 /// bind_password: String::from("password"),
231 /// ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
232 /// dn_attribute: None,
233 /// connection_settings: None
234 /// };
235 ///
236 /// let mut client = LdapClient::new(ldap_config).await.unwrap();
237 /// let name_filter = EqFilter::from("cn".to_string(), "Sam".to_string());
238 ///
239 /// let result = client.authenticate("", "Sam", "password", Box::new(name_filter)).await;
240 /// }
241 /// ```
242 pub async fn authenticate(
243 &mut self,
244 base: &str,
245 uid: &str,
246 password: &str,
247 filter: Box<dyn Filter>,
248 ) -> Result<(), Error> {
249 let attr_dn = self
250 .dn_attr
251 .as_ref()
252 .map(|a| a.as_str())
253 .unwrap_or(LDAP_ENTRY_DN);
254
255 let rs = self
256 .ldap
257 .search(base, Scope::OneLevel, filter.filter().as_str(), [attr_dn])
258 .await
259 .map_err(|e| Error::Query("Unable to query user for authentication".into(), e))?;
260
261 let (data, _rs) = rs
262 .success()
263 .map_err(|e| Error::Query("Could not find user for authentication".into(), e))?;
264
265 if data.is_empty() {
266 return Err(Error::NotFound(format!("No record found {:?}", uid)));
267 }
268 if data.len() > 1 {
269 return Err(Error::MultipleResults(format!(
270 "Found multiple records for uid {:?}",
271 uid
272 )));
273 }
274
275 let record = data.first().unwrap().to_owned();
276 let record = SearchEntry::construct(record);
277 let result: HashMap<&str, String> = record
278 .attrs
279 .iter()
280 .filter(|(_, value)| !value.is_empty())
281 .map(|(arrta, value)| (arrta.as_str(), value.first().unwrap().clone()))
282 .collect();
283
284 let entry_dn = result.get(attr_dn).ok_or_else(|| {
285 Error::AuthenticationFailed(format!("Unable to retrieve DN of user {uid}"))
286 })?;
287
288 self.ldap
289 .simple_bind(entry_dn, password)
290 .await
291 .map_err(|_| {
292 Error::AuthenticationFailed(format!("Error authenticating user: {:?}", uid))
293 })
294 .and_then(|r| {
295 r.success().map_err(|_| {
296 Error::AuthenticationFailed(format!("Error authenticating user: {:?}", uid))
297 })
298 })
299 .and(Ok(()))
300 }
301
302 async fn search_innter(
303 &mut self,
304 base: &str,
305 scope: Scope,
306 filter: &(impl Filter + ?Sized),
307 attributes: &Vec<&str>,
308 ) -> Result<SearchEntry, Error> {
309 let search = self
310 .ldap
311 .search(base, scope, filter.filter().as_str(), attributes)
312 .await;
313 if let Err(error) = search {
314 return Err(Error::Query(
315 format!("Error searching for record: {:?}", error),
316 error,
317 ));
318 }
319 let result = search.unwrap().success();
320 if let Err(error) = result {
321 return Err(Error::Query(
322 format!("Error searching for record: {:?}", error),
323 error,
324 ));
325 }
326
327 let records = result.unwrap().0;
328
329 if records.len() > 1 {
330 return Err(Error::MultipleResults(String::from(
331 "Found multiple records for the search criteria",
332 )));
333 }
334
335 if records.is_empty() {
336 return Err(Error::NotFound(String::from(
337 "No records found for the search criteria",
338 )));
339 }
340
341 let record = records.first().unwrap();
342
343 Ok(SearchEntry::construct(record.to_owned()))
344 }
345
346 ///
347 /// Search a single value from the LDAP server. The search is performed using the provided filter.
348 /// The filter should be a filter that matches a single record. if the filter matches multiple users, an error is returned.
349 /// This operatrion is useful when records has single value attributes.
350 /// Result will be mapped to a struct of type T.
351 ///
352 /// # Arguments
353 ///
354 /// * `base` - The base DN to search for the user
355 /// * `scope` - The scope of the search
356 /// * `filter` - The filter to search for the user
357 /// * `attributes` - The attributes to return from the search
358 ///
359 ///
360 /// # Returns
361 ///
362 /// * `Result<T, Error>` - The result will be mapped to a struct of type T
363 ///
364 ///
365 /// # Example
366 ///
367 /// ```no_run
368 /// use simple_ldap::{
369 /// LdapClient, LdapConfig,
370 /// filter::EqFilter,
371 /// ldap3::Scope
372 /// };
373 /// use url::Url;
374 /// use serde::Deserialize;
375 ///
376 ///
377 /// #[derive(Debug, Deserialize)]
378 /// struct User {
379 /// uid: String,
380 /// cn: String,
381 /// sn: String,
382 /// }
383 ///
384 /// #[tokio::main]
385 /// async fn main(){
386 /// let ldap_config = LdapConfig {
387 /// bind_dn: String::from("cn=manager"),
388 /// bind_password: String::from("password"),
389 /// ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
390 /// dn_attribute: None,
391 /// connection_settings: None
392 /// };
393 ///
394 /// let mut client = LdapClient::new(ldap_config).await.unwrap();
395 ///
396 /// let name_filter = EqFilter::from("cn".to_string(), "Sam".to_string());
397 /// let user_result = client
398 /// .search::<User>(
399 /// "ou=people,dc=example,dc=com",
400 /// Scope::OneLevel,
401 /// &name_filter,
402 /// &vec!["cn", "sn", "uid"],
403 /// ).await;
404 /// }
405 /// ```
406 ///
407 pub async fn search<T: for<'a> serde::Deserialize<'a>>(
408 &mut self,
409 base: &str,
410 scope: Scope,
411 filter: &impl Filter,
412 attributes: &Vec<&str>,
413 ) -> Result<T, Error> {
414 let search_entry = self.search_innter(base, scope, filter, attributes).await?;
415 to_signle_value(search_entry)
416 }
417
418 ///
419 /// Search a single value from the LDAP server. The search is performed using the provided filter.
420 /// The filter should be a filter that matches a single record. if the filter matches multiple users, an error is returned.
421 /// This operatrion is useful when records has multi-valued attributes.
422 ///
423 /// # Arguments
424 ///
425 /// * `base` - The base DN to search for the user
426 /// * `scope` - The scope of the search
427 /// * `filter` - The filter to search for the user
428 /// * `attributes` - The attributes to return from the search
429 ///
430 ///
431 /// # Returns
432 ///
433 /// * `Result<T, Error>` - The result will be mapped to a struct of type T
434 ///
435 ///
436 /// # Example
437 ///
438 /// ```no_run
439 /// use simple_ldap::{
440 /// LdapClient, LdapConfig,
441 /// filter::EqFilter,
442 /// ldap3::Scope
443 /// };
444 /// use url::Url;
445 /// use serde::Deserialize;
446 ///
447 ///
448 /// #[derive(Debug, Deserialize)]
449 /// struct TestMultiValued {
450 /// key1: Vec<String>,
451 /// key2: Vec<String>,
452 /// }
453 ///
454 /// #[tokio::main]
455 /// async fn main(){
456 /// let ldap_config = LdapConfig {
457 /// bind_dn: String::from("cn=manager"),
458 /// bind_password: String::from("password"),
459 /// ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
460 /// dn_attribute: None,
461 /// connection_settings: None
462 /// };
463 ///
464 /// let mut client = LdapClient::new(ldap_config).await.unwrap();
465 ///
466 /// let name_filter = EqFilter::from("cn".to_string(), "Sam".to_string());
467 /// let user_result = client.search_multi_valued::<TestMultiValued>(
468 /// "",
469 /// Scope::OneLevel,
470 /// &name_filter,
471 /// &vec!["cn", "sn", "uid"]
472 /// ).await;
473 /// }
474 /// ```
475 ///
476 pub async fn search_multi_valued<T: for<'a> serde::Deserialize<'a>>(
477 &mut self,
478 base: &str,
479 scope: Scope,
480 filter: &impl Filter,
481 attributes: &Vec<&str>,
482 ) -> Result<T, Error> {
483 let search_entry = self.search_innter(base, scope, filter, attributes).await?;
484 to_multi_value(search_entry)
485 }
486
487 async fn streaming_search_inner<'a>(
488 &mut self,
489 base: &'a str,
490 scope: Scope,
491 filter: &'a (impl Filter + ?Sized),
492 attributes: &'a Vec<&'a str>,
493 ) -> Result<Stream<'a, &'a str, &'a Vec<&'a str>>, Error> {
494 let search_stream: Result<SearchStream<'_, &str, &Vec<&str>>, LdapError> = self
495 .ldap
496 .streaming_search(base, scope, filter.filter().as_str(), attributes)
497 .await;
498 if let Err(error) = search_stream {
499 return Err(Error::Query(
500 format!("Error searching for record: {:?}", error),
501 error,
502 ));
503 }
504
505 let search_stream = search_stream.unwrap();
506
507 let stream = Stream::new(search_stream);
508 Ok(stream)
509 }
510
511 ///
512 /// This method is used to search multiple records from the LDAP server. The search is performed using the provided filter.
513 /// Method will return a Stream. The stream will lazily fetch batches of results resulting in a smaller
514 /// memory footprint.
515 ///
516 /// Prefer streaming search if you don't know that the result set is going to be small.
517 ///
518 ///
519 /// # Arguments
520 ///
521 /// * `base` - The base DN to search for the user
522 /// * `scope` - The scope of the search
523 /// * `filter` - The filter to search for the user
524 /// * `attributes` - The attributes to return from the search
525 ///
526 ///
527 /// # Returns
528 ///
529 /// * `Result<Stream, Error>` - The result will be mapped to a Stream. **Remember to `cleanup()` it when you're done!**
530 ///
531 ///
532 /// # Example
533 ///
534 /// ```no_run
535 /// use simple_ldap::{
536 /// LdapClient, LdapConfig,
537 /// filter::EqFilter,
538 /// ldap3::Scope
539 /// };
540 /// use url::Url;
541 /// use serde::Deserialize;
542 /// use futures::StreamExt;
543 ///
544 ///
545 /// #[derive(Deserialize, Debug)]
546 /// struct User {
547 /// uid: String,
548 /// cn: String,
549 /// sn: String,
550 /// }
551 ///
552 /// #[tokio::main]
553 /// async fn main(){
554 /// let ldap_config = LdapConfig {
555 /// bind_dn: String::from("cn=manager"),
556 /// bind_password: String::from("password"),
557 /// ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
558 /// dn_attribute: None,
559 /// connection_settings: None
560 /// };
561 ///
562 /// let mut client = LdapClient::new(ldap_config).await.unwrap();
563 ///
564 /// let name_filter = EqFilter::from(String::from("cn"), String::from("Sam"));
565 /// let attributes = vec!["cn", "sn", "uid"];
566 ///
567 /// let mut stream = client.streaming_search(
568 /// "",
569 /// Scope::OneLevel,
570 /// &name_filter,
571 /// &attributes
572 /// ).await.unwrap();
573 ///
574 /// while let Some(result) = stream.next().await {
575 /// match result {
576 /// Ok(element) => {
577 /// let user: User = element.to_record().unwrap();
578 /// println!("User: {:?}", user);
579 /// }
580 /// Err(err) => {
581 /// println!("Error: {:?}", err);
582 /// }
583 /// }
584 /// }
585 /// stream.cleanup().await;
586 /// }
587 /// ```
588 ///
589 pub async fn streaming_search<'a>(
590 // This self reference lifetime has some nuance behind it.
591 //
592 // In principle it could just be a value, but then you wouldn't be able to call this
593 // with a pooled client, as the deadpool `Object` wrapper only ever gives out references.
594 //
595 // The lifetime is needed to guarantee that the client is not returned to the pool before
596 // the returned stream is finished. This requirement is artificial. Internally the `ldap3` client
597 // just makes copy. So this lifetime is here just to enforce correct pool usage.
598 &'a mut self,
599 base: &'a str,
600 scope: Scope,
601 filter: &'a impl Filter,
602 attributes: &'a Vec<&'a str>,
603 ) -> Result<Stream<'a, &'a str, &'a Vec<&'a str>>, Error> {
604 let entry = self
605 .streaming_search_inner(base, scope, filter, attributes)
606 .await?;
607
608 Ok(entry)
609 }
610
611 ///
612 /// This method is used to search multiple records from the LDAP server and results will be paginated.
613 /// Method will return a Stream. The stream will lazily fetch batches of results resulting in a smaller
614 /// memory footprint.
615 ///
616 /// Prefer streaming search if you don't know that the result set is going to be small.
617 ///
618 ///
619 /// # Arguments
620 ///
621 /// * `base` - The base DN to search for the user
622 /// * `scope` - The scope of the search
623 /// * `filter` - The filter to search for the user
624 /// * `page_size` - The maximum number of records in a page
625 /// * `attributes` - The attributes to return from the search
626 ///
627 ///
628 /// # Returns
629 ///
630 /// * `Result<Stream, Error>` - A stream that can be used to iterate through the search results.
631 ///
632 ///
633 /// # Example
634 ///
635 /// ```no_run
636 /// use simple_ldap::{
637 /// LdapClient, LdapConfig,
638 /// filter::EqFilter,
639 /// ldap3::Scope
640 /// };
641 /// use url::Url;
642 /// use serde::Deserialize;
643 /// use futures::StreamExt;
644 ///
645 ///
646 /// #[derive(Deserialize, Debug)]
647 /// struct User {
648 /// uid: String,
649 /// cn: String,
650 /// sn: String,
651 /// }
652 ///
653 /// #[tokio::main]
654 /// async fn main(){
655 /// let ldap_config = LdapConfig {
656 /// bind_dn: String::from("cn=manager"),
657 /// bind_password: String::from("password"),
658 /// ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
659 /// dn_attribute: None,
660 /// connection_settings: None
661 /// };
662 ///
663 /// let mut client = LdapClient::new(ldap_config).await.unwrap();
664 ///
665 /// let name_filter = EqFilter::from(String::from("cn"), String::from("Sam"));
666 /// let attributes = vec!["cn", "sn", "uid"];
667 ///
668 /// let mut stream = client.streaming_search_with(
669 /// "",
670 /// Scope::OneLevel,
671 /// &name_filter,
672 /// &attributes,
673 /// 200 // The pagesize
674 /// ).await.unwrap();
675 ///
676 /// while let Some(result) = stream.next().await {
677 /// match result {
678 /// Ok(element) => {
679 /// let user: User = element.to_record().unwrap();
680 /// println!("User: {:?}", user);
681 /// }
682 /// Err(err) => {
683 /// println!("Error: {:?}", err);
684 /// }
685 /// }
686 /// }
687 /// stream.cleanup().await;
688 /// }
689 /// ```
690 ///
691 pub async fn streaming_search_with<'a>(
692 // This self reference lifetime has some nuance behind it.
693 //
694 // In principle it could just be a value, but then you wouldn't be able to call this
695 // with a pooled client, as the deadpool `Object` wrapper only ever gives out references.
696 //
697 // The lifetime is needed to guarantee that the client is not returned to the pool before
698 // the returned stream is finished. This requirement is artificial. Internally the `ldap3` client
699 // just makes copy. So this lifetime is here just to enforce correct pool usage.
700 &'a mut self,
701 base: &'a str,
702 scope: Scope,
703 filter: &'a (impl Filter + ?Sized),
704 attributes: &'a Vec<&'a str>,
705 page_size: i32,
706 ) -> Result<Stream<'a, &'a str, &'a Vec<&'a str>>, Error> {
707 let adapters: Vec<Box<dyn Adapter<_, _>>> = vec![
708 Box::new(EntriesOnly::new()),
709 Box::new(PagedResults::new(page_size)),
710 ];
711 let search_stream = self
712 .ldap
713 .streaming_search_with(adapters, base, scope, filter.filter().as_str(), attributes)
714 .await;
715
716 if let Err(error) = search_stream {
717 return Err(Error::Query(
718 format!("Error searching for record: {:?}", error),
719 error,
720 ));
721 }
722
723 let search_stream = search_stream.unwrap();
724
725 let stream = Stream::new(search_stream);
726 Ok(stream)
727 }
728
729 ///
730 /// Create a new record in the LDAP server. The record will be created in the provided base DN.
731 ///
732 /// # Arguments
733 ///
734 /// * `uid` - The uid of the record
735 /// * `base` - The base DN to create the record
736 /// * `data` - The attributes of the record
737 ///
738 ///
739 /// # Returns
740 ///
741 /// * `Result<(), Error>` - Returns an error if the record creation fails
742 ///
743 ///
744 /// # Example
745 ///
746 /// ```no_run
747 /// use simple_ldap::{LdapClient, LdapConfig};
748 /// use url::Url;
749 /// use std::collections::HashSet;
750 ///
751 /// #[tokio::main]
752 /// async fn main(){
753 /// let ldap_config = LdapConfig {
754 /// bind_dn: String::from("cn=manager"),
755 /// bind_password: String::from("password"),
756 /// ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
757 /// dn_attribute: None,
758 /// connection_settings: None
759 /// };
760 ///
761 /// let mut client = LdapClient::new(ldap_config).await.unwrap();
762 ///
763 /// let data = vec![
764 /// ( "objectClass",HashSet::from(["organizationalPerson", "inetorgperson", "top", "person"]),),
765 /// ("uid",HashSet::from(["bd9b91ec-7a69-4166-bf67-cc7e553b2fd9"]),),
766 /// ("cn", HashSet::from(["Kasun"])),
767 /// ("sn", HashSet::from(["Ranasingh"])),
768 /// ];
769 ///
770 /// let result = client.create("bd9b91ec-7a69-4166-bf67-cc7e553b2fd9", "ou=people,dc=example,dc=com", data).await;
771 /// }
772 /// ```
773 ///
774 pub async fn create(
775 &mut self,
776 uid: &str,
777 base: &str,
778 data: Vec<(&str, HashSet<&str>)>,
779 ) -> Result<(), Error> {
780 let dn = format!("uid={},{}", uid, base);
781 let save = self.ldap.add(dn.as_str(), data).await;
782 if let Err(err) = save {
783 return Err(Error::Create(
784 format!("Error saving record: {:?}", err),
785 err,
786 ));
787 }
788 let save = save.unwrap().success();
789
790 if let Err(err) = save {
791 return Err(Error::Create(
792 format!("Error saving record: {:?}", err),
793 err,
794 ));
795 }
796 let res = save.unwrap();
797 debug!("Sucessfully created record result: {:?}", res);
798 Ok(())
799 }
800
801 ///
802 /// Update a record in the LDAP server. The record will be updated in the provided base DN.
803 ///
804 /// # Arguments
805 ///
806 /// * `uid` - The uid of the record
807 /// * `base` - The base DN to update the record
808 /// * `data` - The attributes of the record
809 /// * `new_uid` - The new uid of the record. If the new uid is provided, the uid of the record will be updated.
810 ///
811 ///
812 /// # Returns
813 ///
814 /// * `Result<(), Error>` - Returns an error if the record update fails
815 ///
816 ///
817 /// # Example
818 ///
819 /// ```no_run
820 /// use simple_ldap::{
821 /// LdapClient, LdapConfig,
822 /// ldap3::Mod
823 /// };
824 /// use url::Url;
825 /// use std::collections::HashSet;
826 ///
827 /// #[tokio::main]
828 /// async fn main(){
829 /// let ldap_config = LdapConfig {
830 /// bind_dn: String::from("cn=manager"),
831 /// bind_password: String::from("password"),
832 /// ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
833 /// dn_attribute: None,
834 /// connection_settings: None
835 /// };
836 ///
837 /// let mut client = LdapClient::new(ldap_config).await.unwrap();
838 ///
839 /// let data = vec![
840 /// Mod::Replace("cn", HashSet::from(["Jhon_Update"])),
841 /// Mod::Replace("sn", HashSet::from(["Eliet_Update"])),
842 /// ];
843 ///
844 /// let result = client.update(
845 /// "e219fbc0-6df5-4bc3-a6ee-986843bb157e",
846 /// "ou=people,dc=example,dc=com",
847 /// data,
848 /// None
849 /// ).await;
850 /// }
851 /// ```
852 ///
853 pub async fn update(
854 &mut self,
855 uid: &str,
856 base: &str,
857 data: Vec<Mod<&str>>,
858 new_uid: Option<&str>,
859 ) -> Result<(), Error> {
860 let dn = format!("uid={},{}", uid, base);
861
862 let res = self.ldap.modify(dn.as_str(), data).await;
863 if let Err(err) = res {
864 return Err(Error::Update(
865 format!("Error updating record: {:?}", err),
866 err,
867 ));
868 }
869
870 let res = res.unwrap().success();
871 if let Err(err) = res {
872 match err {
873 LdapError::LdapResult { result } => {
874 if result.rc == NO_SUCH_RECORD {
875 return Err(Error::NotFound(format!(
876 "No records found for the uid: {:?}",
877 uid
878 )));
879 }
880 }
881 _ => {
882 return Err(Error::Update(
883 format!("Error updating record: {:?}", err),
884 err,
885 ));
886 }
887 }
888 }
889
890 if new_uid.is_none() {
891 return Ok(());
892 }
893
894 let new_uid = new_uid.unwrap();
895 if !uid.eq_ignore_ascii_case(new_uid) {
896 let new_dn = format!("uid={}", new_uid);
897 let dn_update = self
898 .ldap
899 .modifydn(dn.as_str(), new_dn.as_str(), true, None)
900 .await;
901 if let Err(err) = dn_update {
902 error!("Failed to update dn for record {:?} error {:?}", uid, err);
903 return Err(Error::Update(
904 format!("Failed to update dn for record {:?}", uid),
905 err,
906 ));
907 }
908
909 let dn_update = dn_update.unwrap().success();
910 if let Err(err) = dn_update {
911 error!("Failed to update dn for record {:?} error {:?}", uid, err);
912 return Err(Error::Update(
913 format!("Failed to update dn for record {:?}", uid),
914 err,
915 ));
916 }
917
918 let res = dn_update.unwrap();
919 debug!("Sucessfully updated dn result: {:?}", res);
920 }
921
922 Ok(())
923 }
924
925 ///
926 /// Delete a record in the LDAP server. The record will be deleted in the provided base DN.
927 ///
928 /// # Arguments
929 ///
930 /// * `uid` - The uid of the record
931 /// * `base` - The base DN to delete the record
932 ///
933 ///
934 /// # Returns
935 ///
936 /// * `Result<(), Error>` - Returns an error if the record delete fails
937 ///
938 ///
939 /// # Example
940 ///
941 /// ```no_run
942 /// use simple_ldap::{LdapClient, LdapConfig};
943 /// use url::Url;
944 ///
945 /// #[tokio::main]
946 /// async fn main(){
947 /// let ldap_config = LdapConfig {
948 /// bind_dn: String::from("cn=manager"),
949 /// bind_password: String::from("password"),
950 /// ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
951 /// dn_attribute: None,
952 /// connection_settings: None
953 /// };
954 ///
955 /// let mut client = LdapClient::new(ldap_config).await.unwrap();
956 ///
957 /// let result = client.delete("e219fbc0-6df5-4bc3-a6ee-986843bb157e", "ou=people,dc=example,dc=com").await;
958 /// }
959 /// ```
960 pub async fn delete(&mut self, uid: &str, base: &str) -> Result<(), Error> {
961 let dn = format!("uid={},{}", uid, base);
962 let delete = self.ldap.delete(dn.as_str()).await;
963
964 if let Err(err) = delete {
965 return Err(Error::Delete(
966 format!("Error deleting record: {:?}", err),
967 err,
968 ));
969 }
970 let delete = delete.unwrap().success();
971 if let Err(err) = delete {
972 match err {
973 LdapError::LdapResult { result } => {
974 if result.rc == NO_SUCH_RECORD {
975 return Err(Error::NotFound(format!(
976 "No records found for the uid: {:?}",
977 uid
978 )));
979 }
980 }
981 _ => {
982 return Err(Error::Delete(
983 format!("Error deleting record: {:?}", err),
984 err,
985 ));
986 }
987 }
988 }
989 debug!("Sucessfully deleted record result: {:?}", uid);
990 Ok(())
991 }
992
993 ///
994 /// Create a new group in the LDAP server. The group will be created in the provided base DN.
995 ///
996 /// # Arguments
997 ///
998 /// * `group_name` - The name of the group
999 /// * `group_ou` - The ou of the group
1000 /// * `description` - The description of the group
1001 ///
1002 /// # Returns
1003 ///
1004 /// * `Result<(), Error>` - Returns an error if the group creation fails
1005 ///
1006 ///
1007 /// # Example
1008 ///
1009 /// ```no_run
1010 /// use simple_ldap::{LdapClient, LdapConfig};
1011 /// use url::Url;
1012 ///
1013 /// #[tokio::main]
1014 /// async fn main(){
1015 /// let ldap_config = LdapConfig {
1016 /// bind_dn: String::from("cn=manager"),
1017 /// bind_password: String::from("password"),
1018 /// ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1019 /// dn_attribute: None,
1020 /// connection_settings: None
1021 /// };
1022 ///
1023 /// let mut client = LdapClient::new(ldap_config).await.unwrap();
1024 ///
1025 /// let result = client.create_group("test_group", "ou=groups,dc=example,dc=com", "test group").await;
1026 /// }
1027 /// ```
1028 pub async fn create_group(
1029 &mut self,
1030 group_name: &str,
1031 group_ou: &str,
1032 description: &str,
1033 ) -> Result<(), Error> {
1034 let dn = format!("cn={},{}", group_name, group_ou);
1035
1036 let data = vec![
1037 ("objectClass", HashSet::from(["top", "groupOfNames"])),
1038 ("cn", HashSet::from([group_name])),
1039 ("ou", HashSet::from([group_ou])),
1040 ("description", HashSet::from([description])),
1041 ];
1042 let save = self.ldap.add(dn.as_str(), data).await;
1043 if let Err(err) = save {
1044 return Err(Error::Create(
1045 format!("Error saving record: {:?}", err),
1046 err,
1047 ));
1048 }
1049 let save = save.unwrap().success();
1050
1051 if let Err(err) = save {
1052 return Err(Error::Create(
1053 format!("Error creating group: {:?}", err),
1054 err,
1055 ));
1056 }
1057 let res = save.unwrap();
1058 debug!("Sucessfully created group result: {:?}", res);
1059 Ok(())
1060 }
1061
1062 ///
1063 /// Add users to a group in the LDAP server. The group will be updated in the provided base DN.
1064 ///
1065 /// # Arguments
1066 ///
1067 /// * `users` - The list of users to add to the group
1068 /// * `group_dn` - The dn of the group
1069 ///
1070 ///
1071 /// # Returns
1072 ///
1073 /// * `Result<(), Error>` - Returns an error if failed to add users to the group
1074 ///
1075 ///
1076 /// # Example
1077 ///
1078 /// ```no_run
1079 /// use simple_ldap::{LdapClient, LdapConfig};
1080 /// use url::Url;
1081 ///
1082 /// #[tokio::main]
1083 /// async fn main(){
1084 /// let ldap_config = LdapConfig {
1085 /// bind_dn: String::from("cn=manager"),
1086 /// bind_password: String::from("password"),
1087 /// ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1088 /// dn_attribute: None,
1089 /// connection_settings: None
1090 /// };
1091 ///
1092 /// let mut client = LdapClient::new(ldap_config).await.unwrap();
1093 ///
1094 /// let result = client.add_users_to_group(
1095 /// vec!["uid=bd9b91ec-7a69-4166-bf67-cc7e553b2fd9,ou=people,dc=example,dc=com"],
1096 /// "cn=test_group,ou=groups,dc=example,dc=com").await;
1097 /// }
1098 /// ```
1099 pub async fn add_users_to_group(
1100 &mut self,
1101 users: Vec<&str>,
1102 group_dn: &str,
1103 ) -> Result<(), Error> {
1104 let mut mods = Vec::new();
1105 let users = users.iter().copied().collect::<HashSet<&str>>();
1106 mods.push(Mod::Replace("member", users));
1107 let res = self.ldap.modify(group_dn, mods).await;
1108 if let Err(err) = res {
1109 return Err(Error::Update(
1110 format!("Error updating record: {:?}", err),
1111 err,
1112 ));
1113 }
1114
1115 let res = res.unwrap().success();
1116 if let Err(err) = res {
1117 match err {
1118 LdapError::LdapResult { result } => {
1119 if result.rc == NO_SUCH_RECORD {
1120 return Err(Error::NotFound(format!(
1121 "No records found for the uid: {:?}",
1122 group_dn
1123 )));
1124 }
1125 }
1126 _ => {
1127 return Err(Error::Update(
1128 format!("Error updating record: {:?}", err),
1129 err,
1130 ));
1131 }
1132 }
1133 }
1134 Ok(())
1135 }
1136
1137 ///
1138 /// Get users of a group in the LDAP server. The group will be searched in the provided base DN.
1139 ///
1140 /// # Arguments
1141 ///
1142 /// * `group_dn` - The dn of the group
1143 /// * `base_dn` - The base dn to search for the users
1144 /// * `scope` - The scope of the search
1145 /// * `attributes` - The attributes to return from the search
1146 ///
1147 ///
1148 /// # Returns
1149 ///
1150 /// * `Result<Vec<T>, Error>` - Returns a vector of structs of type T
1151 ///
1152 ///
1153 /// # Example
1154 ///
1155 /// ```no_run
1156 /// use simple_ldap::{
1157 /// LdapClient, LdapConfig,
1158 /// ldap3::Scope
1159 /// };
1160 /// use url::Url;
1161 /// use serde::Deserialize;
1162 ///
1163 /// #[derive(Debug, Deserialize)]
1164 /// struct User {
1165 /// uid: String,
1166 /// cn: String,
1167 /// sn: String,
1168 /// }
1169 ///
1170 /// #[tokio::main]
1171 /// async fn main(){
1172 /// let ldap_config = LdapConfig {
1173 /// bind_dn: String::from("cn=manager"),
1174 /// bind_password: String::from("password"),
1175 /// ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1176 /// dn_attribute: None,
1177 /// connection_settings: None
1178 /// };
1179 ///
1180 /// let mut client = LdapClient::new(ldap_config).await.unwrap();
1181 ///
1182 /// let result = client.get_members::<User>(
1183 /// "cn=test_group,ou=groups,dc=example,dc=com",
1184 /// "ou=people,dc=example,dc=com",
1185 /// Scope::OneLevel,
1186 /// &vec!["cn", "sn", "uid"]
1187 /// ).await;
1188 /// }
1189 /// ```
1190 ///
1191 pub async fn get_members<T: for<'a> serde::Deserialize<'a>>(
1192 &mut self,
1193 group_dn: &str,
1194 base_dn: &str,
1195 scope: Scope,
1196 attributes: &Vec<&str>,
1197 ) -> Result<Vec<T>, Error> {
1198 let search = self
1199 .ldap
1200 .search(
1201 group_dn,
1202 Scope::Base,
1203 "(objectClass=groupOfNames)",
1204 vec!["member"],
1205 )
1206 .await;
1207
1208 if let Err(error) = search {
1209 return Err(Error::Query(
1210 format!("Error searching for record: {:?}", error),
1211 error,
1212 ));
1213 }
1214 let result = search.unwrap().success();
1215 if let Err(error) = result {
1216 return Err(Error::Query(
1217 format!("Error searching for record: {:?}", error),
1218 error,
1219 ));
1220 }
1221
1222 let records = result.unwrap().0;
1223
1224 if records.len() > 1 {
1225 return Err(Error::MultipleResults(String::from(
1226 "Found multiple records for the search criteria",
1227 )));
1228 }
1229
1230 if records.is_empty() {
1231 return Err(Error::NotFound(String::from(
1232 "No records found for the search criteria",
1233 )));
1234 }
1235
1236 let record = records.first().unwrap();
1237
1238 let x = SearchEntry::construct(record.to_owned());
1239 let result: HashMap<&str, Vec<String>> = x
1240 .attrs
1241 .iter()
1242 .filter(|(_, value)| !value.is_empty())
1243 .map(|(arrta, value)| (arrta.as_str(), value.to_owned()))
1244 .collect();
1245
1246 let mut members = Vec::new();
1247 for member in result.get("member").unwrap() {
1248 let uid = member.split(',').collect::<Vec<&str>>()[0]
1249 .split('=')
1250 .collect::<Vec<&str>>();
1251 let filter = EqFilter::from(uid[0].to_string(), uid[1].to_string());
1252 let x = self.search::<T>(base_dn, scope, &filter, attributes).await;
1253 match x {
1254 Ok(x) => {
1255 members.push(x);
1256 }
1257 Err(err) => {
1258 error!("Error getting member {:?} error {:?}", member, err);
1259 }
1260 }
1261 }
1262
1263 Ok(members)
1264 }
1265
1266 ///
1267 /// Remove users from a group in the LDAP server. The group will be updated in the provided base DN.
1268 /// This method will remove all the users provided from the group.
1269 ///
1270 ///
1271 /// # Arguments
1272 ///
1273 /// * `group_dn` - The dn of the group
1274 /// * `users` - The list of users to remove from the group
1275 ///
1276 ///
1277 /// # Returns
1278 ///
1279 /// * `Result<(), Error>` - Returns an error if failed to remove users from the group
1280 ///
1281 ///
1282 /// # Example
1283 ///
1284 /// ```no_run
1285 /// use simple_ldap::{LdapClient, LdapConfig};
1286 /// use url::Url;
1287 /// use std::collections::HashSet;
1288 ///
1289 /// #[tokio::main]
1290 /// async fn main(){
1291 /// let ldap_config = LdapConfig {
1292 /// bind_dn: String::from("cn=manager"),
1293 /// bind_password: String::from("password"),
1294 /// ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1295 /// dn_attribute: None,
1296 /// connection_settings: None
1297 /// };
1298 ///
1299 /// let mut client = LdapClient::new(ldap_config).await.unwrap();
1300 ///
1301 /// let result = client.remove_users_from_group("cn=test_group,ou=groups,dc=example,dc=com",
1302 /// vec!["uid=bd9b91ec-7a69-4166-bf67-cc7e553b2fd9,ou=people,dc=example,dc=com"]).await;
1303 /// }
1304 /// ```
1305 pub async fn remove_users_from_group(
1306 &mut self,
1307 group_dn: &str,
1308 users: Vec<&str>,
1309 ) -> Result<(), Error> {
1310 let mut mods = Vec::new();
1311 let users = users.iter().copied().collect::<HashSet<&str>>();
1312 mods.push(Mod::Delete("member", users));
1313 let res = self.ldap.modify(group_dn, mods).await;
1314 if let Err(err) = res {
1315 return Err(Error::Update(
1316 format!("Error removing users from group:{:?}: {:?}", group_dn, err),
1317 err,
1318 ));
1319 }
1320
1321 let res = res.unwrap().success();
1322 if let Err(err) = res {
1323 match err {
1324 LdapError::LdapResult { result } => {
1325 if result.rc == NO_SUCH_RECORD {
1326 return Err(Error::NotFound(format!(
1327 "No records found for the uid: {:?}",
1328 group_dn
1329 )));
1330 }
1331 }
1332 _ => {
1333 return Err(Error::Update(
1334 format!("Error removing users from group:{:?}: {:?}", group_dn, err),
1335 err,
1336 ));
1337 }
1338 }
1339 }
1340 Ok(())
1341 }
1342
1343 ///
1344 /// Get the groups associated with a user in the LDAP server. The user will be searched in the provided base DN.
1345 ///
1346 /// # Arguments
1347 ///
1348 /// * `group_ou` - The ou to search for the groups
1349 /// * `user_dn` - The dn of the user
1350 ///
1351 /// # Returns
1352 ///
1353 /// * `Result<Vec<String>, Error>` - Returns a vector of group names
1354 ///
1355 ///
1356 /// # Example
1357 ///
1358 /// ```no_run
1359 /// use simple_ldap::{LdapClient, LdapConfig};
1360 /// use url::Url;
1361 ///
1362 /// #[tokio::main]
1363 /// async fn main(){
1364 /// let ldap_config = LdapConfig {
1365 /// bind_dn: String::from("cn=manager"),
1366 /// bind_password: String::from("password"),
1367 /// ldap_url: Url::parse("ldaps://localhost:1389/dc=example,dc=com").unwrap(),
1368 /// dn_attribute: None,
1369 /// connection_settings: None
1370 /// };
1371 ///
1372 /// let mut client = LdapClient::new(ldap_config).await.unwrap();
1373 ///
1374 /// let result = client.get_associtated_groups("ou=groups,dc=example,dc=com",
1375 /// "uid=bd9b91ec-7a69-4166-bf67-cc7e553b2fd9,ou=people,dc=example,dc=com").await;
1376 /// }
1377 /// ```
1378 pub async fn get_associtated_groups(
1379 &mut self,
1380 group_ou: &str,
1381 user_dn: &str,
1382 ) -> Result<Vec<String>, Error> {
1383 let group_filter = Box::new(EqFilter::from(
1384 "objectClass".to_string(),
1385 "groupOfNames".to_string(),
1386 ));
1387
1388 let user_filter = Box::new(EqFilter::from("member".to_string(), user_dn.to_string()));
1389 let mut filter = AndFilter::default();
1390 filter.add(group_filter);
1391 filter.add(user_filter);
1392
1393 let search = self
1394 .ldap
1395 .search(
1396 group_ou,
1397 Scope::Subtree,
1398 filter.filter().as_str(),
1399 vec!["cn"],
1400 )
1401 .await;
1402
1403 if let Err(error) = search {
1404 return Err(Error::Query(
1405 format!("Error searching for record: {:?}", error),
1406 error,
1407 ));
1408 }
1409 let result = search.unwrap().success();
1410 if let Err(error) = result {
1411 return Err(Error::Query(
1412 format!("Error searching for record: {:?}", error),
1413 error,
1414 ));
1415 }
1416
1417 let records = result.unwrap().0;
1418
1419 if records.is_empty() {
1420 return Err(Error::NotFound(String::from(
1421 "User does not belong to any groups",
1422 )));
1423 }
1424
1425 let record = records
1426 .iter()
1427 .map(|record| SearchEntry::construct(record.to_owned()))
1428 .map(|se| se.attrs)
1429 .flat_map(|att| {
1430 att.get("cn")
1431 .unwrap()
1432 .iter()
1433 .map(|x| x.to_owned())
1434 .collect::<Vec<String>>()
1435 })
1436 .collect::<Vec<String>>();
1437
1438 Ok(record)
1439 }
1440}
1441
1442fn to_signle_value<T: for<'a> Deserialize<'a>>(search_entry: SearchEntry) -> Result<T, Error> {
1443 let result: HashMap<&str, serde_value::Value> = search_entry
1444 .attrs
1445 .iter()
1446 .filter(|(_, value)| !value.is_empty())
1447 .map(|(arrta, value)| (arrta.as_str(), map_to_single_value(value.first())))
1448 .collect();
1449
1450 let value = serde_value::Value::Map(
1451 result
1452 .into_iter()
1453 .map(|(k, v)| (serde_value::Value::String(k.to_string()), v))
1454 .collect(),
1455 );
1456
1457 Ok(T::deserialize(value).map_err(|err| {
1458 Error::Mapping(format!(
1459 "Error converting search result to object, {:?}",
1460 err
1461 ))
1462 })?)
1463}
1464
1465fn to_multi_value<T: for<'a> Deserialize<'a>>(search_entry: SearchEntry) -> Result<T, Error> {
1466 let result: HashMap<&str, serde_value::Value> = search_entry
1467 .attrs
1468 .iter()
1469 .filter(|(_, value)| !value.is_empty())
1470 .map(|(arrta, value)| (arrta.as_str(), map_to_multi_value(value)))
1471 .collect();
1472
1473 let value = serde_value::Value::Map(
1474 result
1475 .into_iter()
1476 .map(|(k, v)| (serde_value::Value::String(k.to_string()), v))
1477 .collect(),
1478 );
1479
1480 Ok(T::deserialize(value).map_err(|err| {
1481 Error::Mapping(format!(
1482 "Error converting search result to object, {:?}",
1483 err
1484 ))
1485 })?)
1486}
1487
1488fn map_to_single_value(attra_value: Option<&String>) -> serde_value::Value {
1489 match attra_value {
1490 Some(value) => serde_value::Value::String(value.to_string()),
1491 None => serde_value::Value::Option(Option::None),
1492 }
1493}
1494
1495fn map_to_multi_value(attra_value: &Vec<String>) -> serde_value::Value {
1496 serde_value::Value::Seq(
1497 attra_value
1498 .iter()
1499 .map(|value| serde_value::Value::String(value.to_string()))
1500 .collect(),
1501 )
1502}
1503
1504/// The Stream struct is used to iterate through the search results.
1505/// The stream will return a Record object. The Record object can be used to map the search result to a struct.
1506/// After the stream is finished, the cleanup method should be called to cleanup the stream.
1507///
1508pub struct Stream<'a, S, A> {
1509 search_stream: SearchStream<'a, S, A>,
1510}
1511
1512impl<'a, S, A> Stream<'a, S, A>
1513where
1514 S: AsRef<str> + Send + Sync + 'a,
1515 A: AsRef<[S]> + Send + Sync + 'a,
1516{
1517 fn new(search_stream: SearchStream<'a, S, A>) -> Stream<'a, S, A> {
1518 Stream { search_stream }
1519 }
1520
1521 async fn next_inner(&mut self) -> Result<StreamResult<SearchEntry>, Error> {
1522 let next = self.search_stream.next().await;
1523 if let Err(err) = next {
1524 return Err(Error::Query(
1525 format!("Error getting next record: {:?}", err),
1526 err,
1527 ));
1528 }
1529
1530 if self.search_stream.state() != StreamState::Active {
1531 // self.limit = self.count; // Set the limit to the count, to that poll_next will return None
1532 return Ok(StreamResult::Finished);
1533 }
1534
1535 let entry = next.unwrap();
1536 match entry {
1537 Some(entry) => {
1538 // self.count += 1;
1539 let entry = SearchEntry::construct(entry);
1540 return Ok(StreamResult::Record(entry));
1541 }
1542 None => {
1543 // self.limit = self.count; // Set the limit to the count, to that poll_next will return None
1544 return Ok(StreamResult::Finished);
1545 }
1546 }
1547 }
1548
1549 ///
1550 /// Cleanup the stream. This method should be called after the stream is finished,
1551 /// especially if you're stopping before the stream ends naturally.
1552 ///
1553 /// This method will cleanup the stream and close the connection.
1554 ///
1555 pub async fn cleanup(&mut self) -> Result<(), Error> {
1556 let state = self.search_stream.state();
1557 if state == StreamState::Done || state == StreamState::Closed {
1558 return Ok(());
1559 }
1560 let _res = self.search_stream.finish().await;
1561 let msgid = self.search_stream.ldap_handle().last_id();
1562 let result = self.search_stream.ldap_handle().abandon(msgid).await;
1563
1564 match result {
1565 Ok(_) => {
1566 debug!("Sucessfully abandoned search result: {:?}", msgid);
1567 Ok(())
1568 }
1569 Err(err) => {
1570 error!("Error abandoning search result: {:?}", err);
1571 Err(Error::Abandon(
1572 format!("Error abandoning search result: {:?}", err),
1573 err,
1574 ))
1575 }
1576 }
1577 }
1578}
1579
1580impl<'a, S, A> futures::stream::Stream for Stream<'a, S, A>
1581where
1582 S: AsRef<str> + Send + Sync + 'a,
1583 A: AsRef<[S]> + Send + Sync + 'a,
1584{
1585 type Item = Result<Record, Error>;
1586
1587 fn poll_next(
1588 mut self: Pin<&mut Self>,
1589 cx: &mut Context<'_>,
1590 ) -> Poll<Option<Result<Record, Error>>> {
1591 let poll = self.next_inner().boxed().as_mut().poll(cx);
1592 match poll {
1593 Poll::Ready(result) => match result {
1594 Ok(result) => match result {
1595 StreamResult::Record(record) => Poll::Ready(Some(Ok(Record {
1596 search_entry: record,
1597 }))),
1598 StreamResult::Done => Poll::Ready(None),
1599 StreamResult::Finished => Poll::Ready(None),
1600 },
1601 Err(er) => {
1602 return Poll::Ready(Some(Err(er)));
1603 }
1604 },
1605 Poll::Pending => Poll::Pending,
1606 }
1607 }
1608}
1609
1610/// The Record struct is used to map the search result to a struct.
1611/// The Record struct has a method to_record which will map the search result to a struct.
1612/// The Record struct has a method to_multi_valued_record which will map the search result to a struct with multi valued attributes.
1613pub struct Record {
1614 search_entry: SearchEntry,
1615}
1616
1617impl Record {
1618 ///
1619 /// Create a new Record object with single valued attributes.
1620 /// This is essentially parsing the response records into usable types.
1621 //
1622 // This is kind of missnomer, as we aren't creating records here.
1623 // Perhaps something like "deserialize" would fit better?
1624 pub fn to_record<T: for<'b> serde::Deserialize<'b>>(self) -> Result<T, Error> {
1625 to_signle_value(self.search_entry)
1626 }
1627
1628 pub fn to_multi_valued_record_<T: for<'b> serde::Deserialize<'b>>(self) -> Result<T, Error> {
1629 to_multi_value(self.search_entry)
1630 }
1631}
1632
1633pub enum StreamResult<T> {
1634 Record(T),
1635 Done,
1636 Finished,
1637}
1638
1639///
1640/// The error type for the LDAP client
1641///
1642#[derive(Debug, Error)]
1643pub enum Error {
1644 /// Error occured when performing a LDAP query
1645 #[error("{0}")]
1646 Query(String, #[source] LdapError),
1647 /// No records found for the search criteria
1648 #[error("{0}")]
1649 NotFound(String),
1650 /// Multiple records found for the search criteria
1651 #[error("{0}")]
1652 MultipleResults(String),
1653 /// Authenticating a user failed.
1654 #[error("{0}")]
1655 AuthenticationFailed(String),
1656 /// Error occured when creating a record
1657 #[error("{0}")]
1658 Create(String, #[source] LdapError),
1659 /// Error occured when updating a record
1660 #[error("{0}")]
1661 Update(String, #[source] LdapError),
1662 /// Error occured when deleting a record
1663 #[error("{0}")]
1664 Delete(String, #[source] LdapError),
1665 /// Error occured when mapping the search result to a struct
1666 #[error("{0}")]
1667 Mapping(String),
1668 /// Error occurred while attempting to create an LDAP connection
1669 #[error("{0}")]
1670 Connection(String, #[source] LdapError),
1671 /// Error occurred while attempting to close an LDAP connection.
1672 /// Includes unbind issues.
1673 #[error("{0}")]
1674 Close(String, #[source] LdapError),
1675 /// Error occurred while abandoning the search result
1676 #[error("{0}")]
1677 Abandon(String, #[source] LdapError),
1678}
1679
1680#[cfg(test)]
1681mod tests {
1682 //! Local tests that don't need to connect to a server.
1683
1684 use super::*;
1685 use serde::Deserialize;
1686
1687 #[test]
1688 fn create_json_multi_value_test() {
1689 let mut map: HashMap<String, Vec<String>> = HashMap::new();
1690 map.insert(
1691 "key1".to_string(),
1692 vec!["value1".to_string(), "value2".to_string()],
1693 );
1694 map.insert(
1695 "key2".to_string(),
1696 vec!["value3".to_string(), "value4".to_string()],
1697 );
1698 let entry = SearchEntry {
1699 dn: "dn".to_string(),
1700 attrs: map,
1701 bin_attrs: HashMap::new(),
1702 };
1703
1704 let test = to_multi_value::<TestMultiValued>(entry);
1705 assert!(test.is_ok());
1706 let test = test.unwrap();
1707 assert_eq!(test.key1, vec!["value1".to_string(), "value2".to_string()]);
1708 assert_eq!(test.key2, vec!["value3".to_string(), "value4".to_string()]);
1709 }
1710
1711 #[test]
1712 fn create_json_single_value_test() {
1713 let mut map: HashMap<String, Vec<String>> = HashMap::new();
1714 map.insert("key1".to_string(), vec!["value1".to_string()]);
1715 map.insert("key2".to_string(), vec!["value2".to_string()]);
1716 map.insert("key4".to_string(), vec!["value4".to_string()]);
1717 let entry = SearchEntry {
1718 dn: "dn".to_string(),
1719 attrs: map,
1720 bin_attrs: HashMap::new(),
1721 };
1722
1723 let test = to_signle_value::<TestSingleValued>(entry);
1724 assert!(test.is_ok());
1725 let test = test.unwrap();
1726 assert_eq!(test.key1, "value1".to_string());
1727 assert_eq!(test.key2, "value2".to_string());
1728 assert!(test.key3.is_none());
1729 assert_eq!(test.key4.unwrap(), "value4".to_string());
1730 }
1731
1732 #[derive(Debug, Deserialize)]
1733 struct TestMultiValued {
1734 key1: Vec<String>,
1735 key2: Vec<String>,
1736 }
1737
1738 #[derive(Debug, Deserialize)]
1739 struct TestSingleValued {
1740 key1: String,
1741 key2: String,
1742 key3: Option<String>,
1743 key4: Option<String>,
1744 }
1745}
1746
1747// Add readme examples to doctests:
1748// https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html#include-items-only-when-collecting-doctests
1749#[doc = include_str!("../README.md")]
1750#[cfg(doctest)]
1751pub struct ReadmeDoctests;