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