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}