ldap3_serde/
adapters.rs

1//! Search operation adapters.
2//!
3//! A Search operation can return various objects in addition to directory entries, such as
4//! referrals or intermediate messages, which may or may not be of interest to the user invoking
5//! the operation. Some search operations will effectively span several discrete Search protocol
6//! exchanges, as is the case for searches using the Paged Results control, or distributed
7//! searches with referral chasing. Search adapters provide a mechanism to hand control of the
8//! operation to user-defined code capable of handling such use cases, while presenting the invoker
9//! with the same result-gathering interface.
10//!
11//! An adapter is a struct implementing the [`Adapter`](trait.Adapter.html) trait. A single adapter
12//! struct or a vector of `Adapter` trait objects can be passed to the
13//! [`streaming_search_with()`](../struct.Ldap.html#method.streaming_search_with) method on the `Ldap`
14//! handle along with regular Search parameters to create an adapted search. Calling the stream
15//! methods on the returned handle will execute the chain of `Adapter` methods from each adapter in
16//! turn, ending with the direct call of the regular stream method.
17//!
18//! Adapters must be written with async calls, but work equally well for both async and sync versions of the API
19//! because the sync API is just a blocking façade for the async one.
20
21use std::fmt::Debug;
22use std::marker::PhantomData;
23
24use crate::controls::{self, Control, ControlType};
25use crate::ldap::Ldap;
26use crate::result::{LdapError, LdapResult, Result};
27use crate::search::parse_refs;
28use crate::search::{ResultEntry, Scope, SearchStream};
29
30use async_trait::async_trait;
31
32/// Adapter interface to a Search.
33///
34/// Structs implementing this trait:
35///
36/// * Must additionally implement `Clone` and `Debug`;
37///
38/// * Must be `Send` and `Sync`.
39///
40/// The trait is parametrized with `'a`, the lifetime bound propagated to trait objects, `S`, used in the `start()`
41/// method as the generic type for attribute names, and `A`, the vector of attribute names. (They appear here
42/// because of object safety; `A` enables initialization with owned or borrowed attribute lists.) When implementing the trait,
43/// `S` must be constrained to `AsRef<str> + Send + Sync + 'a`, and `A` to `AsRef<[S]> + Send + Sync + 'a`.
44/// To use a bare instance of a struct implementing this trait in the call to `streaming_search_with()`, the struct
45/// must also implement [`SoloMarker`](trait.SoloMarker.html).
46///
47/// There are three points where an adapter can hook into a Search:
48///
49/// 1. Initialization, which can be used to capture the parameters of the operation
50///    itself and the underlying `Ldap` handle, as well as to prepare the internal adapter state.
51///    This is done in the [`start()`](#tymethod.start) method.
52///
53/// 2. Entry iteration, where each fetched entry can be examined, transformed, or discarded.
54///    The [`next()`](#tymethod.next) method serves this purpose.
55///
56/// 3. Termination, which can examine and transform the result, or invoke further operations
57///    to terminate other active connections or operations, if necessary. The [`finish()`](#tymethod.finish)
58///    method is used for this.
59///
60/// All three methods are called in an async context, so they are marked as `async` and implemented using the
61/// `async_trait` proc macro from the `async-trait` crate. To make chaining work, all trait methods must call
62/// the corresponding method on the passed stream handle.
63///
64/// Additional details of the calling structure are provided in the documentation of the
65/// [`StreamState`](../enum.StreamState.html) enum.
66///
67/// ## Example: the `EntriesOnly` adapter
68///
69/// This adapter discards intermediate messages and collects all referreals in the result of the Search
70/// operation. The (slightly simplified) source is annotated by comments pointing out the notable
71/// details of the implementation.
72///
73/// ```rust,no_run
74/// # use async_trait::async_trait;
75/// # use ldap3::adapters::{Adapter, SoloMarker};
76/// # use ldap3::{ResultEntry, Scope, SearchStream};
77/// # use ldap3::result::{LdapResult, Result};
78/// # use ldap3::parse_refs;
79/// // An adapter must implement Clone and Debug
80/// #[derive(Clone, Debug)]
81/// pub struct EntriesOnly {
82///     refs: Vec<String>,
83/// }
84///
85/// // This impl enables the use of a bare struct instance
86/// // when invoking a Search
87/// impl SoloMarker for EntriesOnly {}
88///
89/// // Adapter impl must be derived with the async_trait proc macro
90/// // until Rust supports async fns in traits directly
91/// #[async_trait]
92/// impl<'a, S, A> Adapter<'a, S, A> for EntriesOnly
93/// where
94///     // The S and A generic parameters must have these bounds
95///     S: AsRef<str> + Send + Sync + 'a,
96///     A: AsRef<[S]> + Send + Sync + 'a,
97/// {
98///     // The start() method doesn't do much
99///     async fn start(
100///         &mut self,
101///         stream: &mut SearchStream<'a, S, A>,
102///         base: &str,
103///         scope: Scope,
104///         filter: &str,
105///         attrs: A,
106///     ) -> Result<()> {
107///         self.refs.clear();
108///         // Call up the adapter chain
109///         stream.start(base, scope, filter, attrs).await
110///     }
111///
112///     // Multiple calls up the chain are possible before
113///     // a single result entry is returned
114///     async fn next(
115///         &mut self,
116///         stream: &mut SearchStream<'a, S, A>
117///     ) -> Result<Option<ResultEntry>> {
118///         loop {
119///             // Call up the adapter chain
120///             return match stream.next().await {
121///                 Ok(None) => Ok(None),
122///                 Ok(Some(re)) => {
123///                     if re.is_intermediate() {
124///                         continue;
125///                     } else if re.is_ref() {
126///                         self.refs.extend(parse_refs(re.0));
127///                         continue;
128///                     } else {
129///                         Ok(Some(re))
130///                     }
131///                 }
132///                 Err(e) => Err(e),
133///             };
134///         }
135///     }
136///
137///     // The result returned from the upcall is modified by our values
138///     async fn finish(&mut self, stream: &mut SearchStream<'a, S, A>) -> LdapResult {
139///         // Call up the adapter chain
140///         let mut res = stream.finish().await;
141///         res.refs.extend(std::mem::take(&mut self.refs));
142///         res
143///     }
144/// }
145#[async_trait]
146pub trait Adapter<'a, S, A>: AdapterClone<'a, S, A> + Debug + Send + Sync + 'a {
147    /// Initialize the stream.
148    async fn start(
149        &mut self,
150        stream: &mut SearchStream<'a, S, A>,
151        base: &str,
152        scope: Scope,
153        filter: &str,
154        attrs: A,
155    ) -> Result<()>;
156
157    /// Fetch the next entry from the stream.
158    async fn next(&mut self, stream: &mut SearchStream<'a, S, A>) -> Result<Option<ResultEntry>>;
159
160    /// Return the result from the stream.
161    async fn finish(&mut self, stream: &mut SearchStream<'a, S, A>) -> LdapResult;
162}
163
164/// Helper trait to enforce `Clone` on `Adapter` implementors.
165pub trait AdapterClone<'a, S, A> {
166    fn box_clone(&self) -> Box<dyn Adapter<'a, S, A> + 'a>;
167}
168
169impl<'a, S, A, T> AdapterClone<'a, S, A> for T
170where
171    T: Adapter<'a, S, A> + Clone + 'a,
172{
173    fn box_clone(&self) -> Box<dyn Adapter<'a, S, A> + 'a> {
174        Box::new(self.clone())
175    }
176}
177
178/// Marker trait for convenient single-adapter searches.
179///
180/// If a struct implements this trait in addition to `Adapter`, its bare instance can appear
181/// as the first argument of [`streaming_search_with()`](../struct.Ldap.html#method.streaming_search_with)
182/// without the need for constructing a single-element vector containing the boxed trait object derived
183/// from the instance.
184pub trait SoloMarker {}
185
186/// Helper trait for `Adapter` instance/chain conversions.
187pub trait IntoAdapterVec<'a, S, A> {
188    fn into(self) -> Vec<Box<dyn Adapter<'a, S, A> + 'a>>;
189}
190
191impl<'a, S, A> IntoAdapterVec<'a, S, A> for Vec<Box<dyn Adapter<'a, S, A> + 'a>> {
192    fn into(self) -> Vec<Box<dyn Adapter<'a, S, A> + 'a>> {
193        self
194    }
195}
196
197impl<'a, Ad, S, A> IntoAdapterVec<'a, S, A> for Ad
198where
199    Ad: Adapter<'a, S, A> + SoloMarker,
200    S: AsRef<str> + Send + Sync + 'a,
201    A: AsRef<[S]> + Send + Sync + 'a,
202{
203    fn into(self) -> Vec<Box<dyn Adapter<'a, S, A> + 'a>> {
204        vec![Box::new(self)]
205    }
206}
207
208/// Adapter which returns just the directory entries.
209///
210/// This adapter mimics the earlier behavior of the crate, where referrals were collected
211/// and returned in the overall result of the Search, and nothing but directory entries
212/// were returned to the users.
213///
214/// To invoke a streaming Search with this adapter on the `ldap` handle, use
215///
216/// ```rust,no_run
217/// # use ldap3::adapters::EntriesOnly;
218/// # use ldap3::{LdapConn, Scope};
219/// # let mut ldap = LdapConn::new("ldapi://ldapi").unwrap();
220/// let mut stream = ldap.streaming_search_with(
221///     EntriesOnly::new(),
222///     "",
223///     Scope::Base,
224///     "(objectClass=*)",
225///     vec!["+"]
226/// );
227/// # let _ = stream;
228/// ```
229#[derive(Clone, Debug)]
230pub struct EntriesOnly {
231    refs: Vec<String>,
232}
233
234/// Create a new adapter instance.
235#[allow(clippy::new_without_default)]
236impl EntriesOnly {
237    pub fn new() -> Self {
238        Self { refs: vec![] }
239    }
240}
241
242impl SoloMarker for EntriesOnly {}
243
244#[async_trait]
245impl<'a, S, A> Adapter<'a, S, A> for EntriesOnly
246where
247    S: AsRef<str> + Send + Sync + 'a,
248    A: AsRef<[S]> + Send + Sync + 'a,
249{
250    async fn start(
251        &mut self,
252        stream: &mut SearchStream<'a, S, A>,
253        base: &str,
254        scope: Scope,
255        filter: &str,
256        attrs: A,
257    ) -> Result<()> {
258        self.refs.clear();
259        stream.start(base, scope, filter, attrs).await
260    }
261
262    async fn next(&mut self, stream: &mut SearchStream<'a, S, A>) -> Result<Option<ResultEntry>> {
263        loop {
264            return match stream.next().await {
265                Ok(None) => Ok(None),
266                Ok(Some(re)) => {
267                    if re.is_intermediate() {
268                        continue;
269                    } else if re.is_ref() {
270                        self.refs.extend(parse_refs(re.0));
271                        continue;
272                    } else {
273                        Ok(Some(re))
274                    }
275                }
276                Err(e) => Err(e),
277            };
278        }
279    }
280
281    async fn finish(&mut self, stream: &mut SearchStream<'a, S, A>) -> LdapResult {
282        let mut res = stream.finish().await;
283        res.refs.extend(std::mem::take(&mut self.refs));
284        res
285    }
286}
287
288/// Adapter which fetches Search results with a Paged Results control.
289///
290/// The adapter adds a Paged Results control with the user-supplied page size to
291/// a Search operation. The operation must not already contain a Paged Results
292/// control; if it does, an error is reported. If the complete result set is not
293/// retrieved in the first protocol operation, the adapter will automatically issue
294/// further Searches until the whole search is done.
295#[derive(Clone, Debug)]
296pub struct PagedResults<S: AsRef<str>, A> {
297    page_size: i32,
298    ldap: Option<Ldap>,
299    base: String,
300    scope: Scope,
301    filter: String,
302    attrs: Option<A>,
303    _s: PhantomData<S>,
304}
305
306impl<S, A> SoloMarker for PagedResults<S, A>
307where
308    S: AsRef<str> + Send + Sync,
309    A: AsRef<[S]> + Send + Sync,
310{
311}
312
313impl<S, A> PagedResults<S, A>
314where
315    S: AsRef<str> + Send + Sync,
316    A: AsRef<[S]> + Send + Sync,
317{
318    /// Construct a new adapter instance with the requested page size.
319    pub fn new(page_size: i32) -> Self {
320        Self {
321            page_size,
322            ldap: None,
323            base: String::from(""),
324            scope: Scope::Base,
325            filter: String::from(""),
326            attrs: None,
327            _s: PhantomData,
328        }
329    }
330}
331
332#[async_trait]
333impl<'a, S, A> Adapter<'a, S, A> for PagedResults<S, A>
334where
335    S: AsRef<str> + Clone + Debug + Send + Sync + 'a,
336    A: AsRef<[S]> + Clone + Debug + Send + Sync + 'a,
337{
338    async fn start(
339        &mut self,
340        stream: &mut SearchStream<'a, S, A>,
341        base: &str,
342        scope: Scope,
343        filter: &str,
344        attrs: A,
345    ) -> Result<()> {
346        let stream_ldap = stream.ldap_handle();
347        let mut ldap = stream_ldap.clone();
348        ldap.timeout = stream_ldap.timeout;
349        ldap.search_opts = stream_ldap.search_opts.clone();
350        let empty_ctrls = vec![];
351        let mut found_pr = false;
352        let mut controls: Vec<_> = stream_ldap
353            .controls
354            .as_ref()
355            .unwrap_or(&empty_ctrls)
356            .iter()
357            .filter(|c| {
358                if c.ctype == "1.2.840.113556.1.4.319" {
359                    found_pr = true;
360                    false
361                } else {
362                    true
363                }
364            })
365            .cloned()
366            .collect();
367        if found_pr {
368            return Err(LdapError::AdapterInit(String::from(
369                "found Paged Results control in op set",
370            )));
371        }
372        ldap.controls = Some(controls.clone());
373        controls.push(
374            controls::PagedResults {
375                size: self.page_size,
376                cookie: vec![],
377            }
378            .into(),
379        );
380        // Not a typo for "stream_ldap", we're replacing Ldap controls.
381        stream.ldap.controls = Some(controls);
382        self.ldap = Some(ldap);
383        self.base = String::from(base);
384        self.scope = scope;
385        self.filter = String::from(filter);
386        self.attrs = Some(attrs.clone());
387        stream.start(base, scope, filter, attrs).await
388    }
389
390    async fn next(&mut self, stream: &mut SearchStream<'a, S, A>) -> Result<Option<ResultEntry>> {
391        'ent: loop {
392            match stream.next().await {
393                Ok(None) => {
394                    let mut pr_index = None;
395                    let ctrls = if let Some(res_ref) = stream.res.as_mut() {
396                        &mut res_ref.ctrls
397                    } else {
398                        return Ok(None);
399                    };
400                    for (cno, ctrl) in ctrls.iter().enumerate() {
401                        if let Control(Some(ControlType::PagedResults), ref raw) = *ctrl {
402                            pr_index = Some(cno);
403                            let pr: controls::PagedResults = raw.parse();
404                            if pr.cookie.is_empty() {
405                                break;
406                            }
407                            let ldap_ref = self.ldap.as_ref().expect("ldap_ref");
408                            let mut ldap = ldap_ref.clone();
409                            ldap.timeout = ldap_ref.timeout;
410                            ldap.search_opts = ldap_ref.search_opts.clone();
411                            let mut controls = ldap_ref.controls.clone().expect("saved ctrls");
412                            controls.push(
413                                controls::PagedResults {
414                                    size: self.page_size,
415                                    cookie: pr.cookie.clone(),
416                                }
417                                .into(),
418                            );
419                            ldap.controls = Some(controls);
420                            let new_stream = match ldap
421                                .streaming_search(
422                                    &self.base,
423                                    self.scope,
424                                    &self.filter,
425                                    self.attrs.as_ref().unwrap(),
426                                )
427                                .await
428                            {
429                                Ok(strm) => strm,
430                                Err(e) => return Err(e),
431                            };
432                            // Again, we're replacing the innards of the original stream with
433                            // the contents of the new one.
434                            stream.ldap = new_stream.ldap;
435                            stream.rx = new_stream.rx;
436                            continue 'ent;
437                        }
438                    }
439                    if let Some(pr_index) = pr_index {
440                        ctrls.remove(pr_index);
441                    }
442                    return Ok(None);
443                }
444                any => return any,
445            }
446        }
447    }
448
449    async fn finish(&mut self, stream: &mut SearchStream<'a, S, A>) -> LdapResult {
450        stream.finish().await
451    }
452}