use futures::{Stream, StreamExt, executor::block_on, stream};
use ldap3::{
Ldap, LdapConnAsync, LdapConnSettings, LdapError, LdapResult, Mod, Scope, SearchEntry,
SearchStream, StreamState,
adapters::{Adapter, EntriesOnly, PagedResults},
};
use serde::{Deserialize, Serialize};
use serde_value::Value;
use std::{
collections::{HashMap, HashSet},
fmt, iter, num::NonZeroU16,
};
use thiserror::Error;
use tracing::{Level, debug, error, info, instrument, warn};
use url::Url;
use filter::{AndFilter, EqFilter, Filter, OrFilter};
use sort::adapter::ServerSideSort;
pub mod filter;
#[cfg(feature = "pool")]
pub mod pool;
pub mod simple_dn;
mod sort;
pub use simple_dn::SimpleDN;
pub use sort::adapter::SortBy;
pub extern crate ldap3;
const LDAP_ENTRY_DN: &str = "entryDN";
const NO_SUCH_RECORD: u32 = 32;
#[derive(derive_more::Debug, Clone)]
pub struct LdapConfig {
pub ldap_url: Url,
pub bind_dn: String,
#[debug(skip)] pub bind_password: String,
pub dn_attribute: Option<String>,
#[debug(skip)] pub connection_settings: Option<LdapConnSettings>,
}
#[derive(Debug, Clone)]
pub struct LdapClient {
ldap: Ldap,
dn_attr: Option<String>,
}
impl LdapClient {
pub async fn new(config: LdapConfig) -> Result<Self, Error> {
debug!("Creating new connection");
let (conn, mut ldap) = match config.connection_settings {
None => LdapConnAsync::from_url(&config.ldap_url).await,
Some(settings) => {
LdapConnAsync::from_url_with_settings(settings, &config.ldap_url).await
}
}
.map_err(|ldap_err| {
Error::Connection(
String::from("Failed to initialize LDAP connection."),
ldap_err,
)
})?;
ldap3::drive!(conn);
ldap.simple_bind(&config.bind_dn, &config.bind_password)
.await
.map_err(|ldap_err| Error::Connection(String::from("Bind failed"), ldap_err))?
.success()
.map_err(|ldap_err| Error::Connection(String::from("Bind failed"), ldap_err))?;
Ok(Self {
dn_attr: config.dn_attribute,
ldap,
})
}
}
impl LdapClient {
#[deprecated = "This abstraction leakage will be removed in a future release.
Use the provided methods instead. If something's missing, open an issue in github."]
pub fn get_inner(&self) -> Ldap {
self.ldap.clone()
}
pub async fn unbind(mut self) -> Result<(), Error> {
match self.ldap.unbind().await {
Ok(_) => Ok(()),
Err(error) => Err(Error::Close(String::from("Failed to unbind"), error)),
}
}
pub async fn authenticate(
&mut self,
base: &str,
uid: &str,
password: &str,
filter: Box<dyn Filter>,
) -> Result<(), Error> {
let attr_dn = self.dn_attr.as_deref().unwrap_or(LDAP_ENTRY_DN);
let rs = self
.ldap
.search(base, Scope::OneLevel, filter.filter().as_str(), [attr_dn])
.await
.map_err(|e| Error::Query("Unable to query user for authentication".into(), e))?;
let (data, _rs) = rs
.success()
.map_err(|e| Error::Query("Could not find user for authentication".into(), e))?;
if data.is_empty() {
return Err(Error::NotFound(format!("No record found {uid:?}")));
}
if data.len() > 1 {
return Err(Error::MultipleResults(format!(
"Found multiple records for uid {uid:?}"
)));
}
let record = data.first().unwrap().to_owned();
let record = SearchEntry::construct(record);
let result: HashMap<&str, String> = record
.attrs
.iter()
.filter(|(_, value)| !value.is_empty())
.map(|(arrta, value)| (arrta.as_str(), value.first().unwrap().clone()))
.collect();
let entry_dn = result.get(attr_dn).ok_or_else(|| {
Error::AuthenticationFailed(format!("Unable to retrieve DN of user {uid}"))
})?;
self.ldap
.simple_bind(entry_dn, password)
.await
.map_err(|_| Error::AuthenticationFailed(format!("Error authenticating user: {uid:?}")))
.and_then(|r| {
r.success().map_err(|_| {
Error::AuthenticationFailed(format!("Error authenticating user: {uid:?}"))
})
})
.and(Ok(()))
}
async fn search_inner<'a, F, A, S>(
&mut self,
base: &str,
scope: Scope,
filter: &F,
attributes: A,
) -> Result<SearchEntry, Error>
where
F: Filter,
A: AsRef<[S]> + Send + Sync + 'a,
S: AsRef<str> + Send + Sync + 'a,
{
let search = self
.ldap
.search(base, scope, filter.filter().as_str(), attributes)
.await;
if let Err(error) = search {
return Err(Error::Query(
format!("Error searching for record: {error:?}"),
error,
));
}
let result = search.unwrap().success();
if let Err(error) = result {
return Err(Error::Query(
format!("Error searching for record: {error:?}"),
error,
));
}
let records = result.unwrap().0;
if records.len() > 1 {
return Err(Error::MultipleResults(String::from(
"Found multiple records for the search criteria",
)));
}
if records.is_empty() {
return Err(Error::NotFound(String::from(
"No records found for the search criteria",
)));
}
let record = records.first().unwrap();
Ok(SearchEntry::construct(record.to_owned()))
}
pub async fn search<'a, F, A, S, T>(
&mut self,
base: &str,
scope: Scope,
filter: &F,
attributes: A,
) -> Result<T, Error>
where
F: Filter,
A: AsRef<[S]> + Send + Sync + 'a,
S: AsRef<str> + Send + Sync + 'a,
T: for<'de> serde::Deserialize<'de>,
{
let search_entry = self.search_inner(base, scope, filter, attributes).await?;
to_value(search_entry)
}
pub async fn search_multi_valued<T: for<'a> serde::Deserialize<'a>>(
&mut self,
base: &str,
scope: Scope,
filter: &impl Filter,
attributes: &Vec<&str>,
) -> Result<T, Error> {
let search_entry = self.search_inner(base, scope, filter, attributes).await?;
to_multi_value(search_entry)
}
pub async fn streaming_search<'a, F, A, S>(
&'a mut self,
base: &str,
scope: Scope,
filter: &F,
attributes: A,
page_size: Option<NonZeroU16>,
sort_by: Vec<SortBy>,
) -> Result<impl Stream<Item = Result<Record, Error>> + use<'a, F, A, S>, Error>
where
F: Filter,
A: AsRef<[S]> + Send + Sync + Clone + fmt::Debug + 'a,
S: AsRef<str> + Send + Sync + Clone + fmt::Debug + 'a,
{
let (paging_adapter, entries_only_adapter) = page_size.map(|non_zero|
(PagedResults::new(non_zero.get().into()), EntriesOnly::new())
)
.map(|(page_adapter, entries_adapter)| (Box::new(page_adapter) as _, Box::new(entries_adapter) as _))
.unzip();
let sort_adapter: Option<Box<dyn Adapter<'a, S, A>>> = vec_to_option(sort_by)
.map(ServerSideSort::new)
.transpose()
.map_err(|duplicate_args_err| Error::Sort(duplicate_args_err.to_string()))?
.map(|adapter| Box::new(adapter) as _);
let maybe_adapters: Vec<Option<Box<dyn Adapter<'a, S, A>>>> = vec![
sort_adapter,
entries_only_adapter,
paging_adapter,
];
let adapters: Vec<_> = maybe_adapters.into_iter().flatten().collect();
let search_stream = self
.ldap
.streaming_search_with(adapters, base, scope, filter.filter().as_str(), attributes)
.await
.map_err(|ldap_error| {
Error::Query(
format!("Error searching for record: {ldap_error:?}"),
ldap_error,
)
})?;
to_native_stream(search_stream)
}
pub async fn create(
&mut self,
uid: &str,
base: &str,
data: Vec<(&str, HashSet<&str>)>,
) -> Result<(), Error> {
let dn = format!("uid={uid},{base}");
let save = self.ldap.add(dn.as_str(), data).await;
if let Err(err) = save {
return Err(Error::Create(format!("Error saving record: {err:?}"), err));
}
let save = save.unwrap().success();
if let Err(err) = save {
return Err(Error::Create(format!("Error saving record: {err:?}"), err));
}
let res = save.unwrap();
debug!("Successfully created record result: {:?}", res);
Ok(())
}
pub async fn update(
&mut self,
uid: &str,
base: &str,
data: Vec<Mod<&str>>,
new_uid: Option<&str>,
) -> Result<(), Error> {
let dn = format!("uid={uid},{base}");
let res = self.ldap.modify(dn.as_str(), data).await;
if let Err(err) = res {
return Err(Error::Update(
format!("Error updating record: {err:?}"),
err,
));
}
let res = res.unwrap().success();
if let Err(err) = res {
match err {
LdapError::LdapResult { result } => {
if result.rc == NO_SUCH_RECORD {
return Err(Error::NotFound(format!(
"No records found for the uid: {uid:?}"
)));
}
}
_ => {
return Err(Error::Update(
format!("Error updating record: {err:?}"),
err,
));
}
}
}
if new_uid.is_none() {
return Ok(());
}
let new_uid = new_uid.unwrap();
if !uid.eq_ignore_ascii_case(new_uid) {
let new_dn = format!("uid={new_uid}");
let dn_update = self
.ldap
.modifydn(dn.as_str(), new_dn.as_str(), true, None)
.await;
if let Err(err) = dn_update {
error!("Failed to update dn for record {:?} error {:?}", uid, err);
return Err(Error::Update(
format!("Failed to update dn for record {uid:?}"),
err,
));
}
let dn_update = dn_update.unwrap().success();
if let Err(err) = dn_update {
error!("Failed to update dn for record {:?} error {:?}", uid, err);
return Err(Error::Update(
format!("Failed to update dn for record {uid:?}"),
err,
));
}
let res = dn_update.unwrap();
debug!("Successfully updated dn result: {:?}", res);
}
Ok(())
}
pub async fn delete(&mut self, uid: &str, base: &str) -> Result<(), Error> {
let dn = format!("uid={uid},{base}");
let delete = self.ldap.delete(dn.as_str()).await;
if let Err(err) = delete {
return Err(Error::Delete(
format!("Error deleting record: {err:?}"),
err,
));
}
let delete = delete.unwrap().success();
if let Err(err) = delete {
match err {
LdapError::LdapResult { result } => {
if result.rc == NO_SUCH_RECORD {
return Err(Error::NotFound(format!(
"No records found for the uid: {uid:?}"
)));
}
}
_ => {
return Err(Error::Delete(
format!("Error deleting record: {err:?}"),
err,
));
}
}
}
debug!("Successfully deleted record result: {:?}", uid);
Ok(())
}
pub async fn create_group(
&mut self,
group_name: &str,
group_ou: &str,
description: &str,
) -> Result<(), Error> {
let dn = format!("cn={group_name},{group_ou}");
let data = vec![
("objectClass", HashSet::from(["top", "groupOfNames"])),
("cn", HashSet::from([group_name])),
("ou", HashSet::from([group_ou])),
("description", HashSet::from([description])),
];
let save = self.ldap.add(dn.as_str(), data).await;
if let Err(err) = save {
return Err(Error::Create(format!("Error saving record: {err:?}"), err));
}
let save = save.unwrap().success();
if let Err(err) = save {
return Err(Error::Create(format!("Error creating group: {err:?}"), err));
}
let res = save.unwrap();
debug!("Successfully created group result: {:?}", res);
Ok(())
}
pub async fn add_users_to_group(
&mut self,
users: Vec<&str>,
group_dn: &str,
) -> Result<(), Error> {
let mut mods = Vec::new();
let users = users.iter().copied().collect::<HashSet<&str>>();
mods.push(Mod::Replace("member", users));
let res = self.ldap.modify(group_dn, mods).await;
if let Err(err) = res {
return Err(Error::Update(
format!("Error updating record: {err:?}"),
err,
));
}
let res = res.unwrap().success();
if let Err(err) = res {
match err {
LdapError::LdapResult { result } => {
if result.rc == NO_SUCH_RECORD {
return Err(Error::NotFound(format!(
"No records found for the uid: {group_dn:?}"
)));
}
}
_ => {
return Err(Error::Update(
format!("Error updating record: {err:?}"),
err,
));
}
}
}
Ok(())
}
pub async fn get_members<'a, A, S, T>(
&mut self,
group_dn: &str,
base_dn: &str,
scope: Scope,
attributes: A,
) -> Result<Vec<T>, Error>
where
A: AsRef<[S]> + Send + Sync + Clone + fmt::Debug + 'a,
S: AsRef<str> + Send + Sync + Clone + fmt::Debug + 'a,
T: for<'de> serde::Deserialize<'de>,
{
let search = self
.ldap
.search(
group_dn,
Scope::Base,
"(objectClass=groupOfNames)",
vec!["member"],
)
.await;
if let Err(error) = search {
return Err(Error::Query(
format!("Error searching for record: {error:?}"),
error,
));
}
let result = search.unwrap().success();
if let Err(error) = result {
return Err(Error::Query(
format!("Error searching for record: {error:?}"),
error,
));
}
let records = result.unwrap().0;
if records.len() > 1 {
return Err(Error::MultipleResults(String::from(
"Found multiple records for the search criteria",
)));
}
if records.is_empty() {
return Err(Error::NotFound(String::from(
"No records found for the search criteria",
)));
}
let record = records.first().unwrap();
let mut or_filter = OrFilter::default();
let search_entry = SearchEntry::construct(record.to_owned());
search_entry
.attrs
.into_iter()
.filter(|(_, value)| !value.is_empty())
.map(|(arrta, value)| (arrta.to_owned(), value.to_owned()))
.filter(|(attra, _)| attra.eq("member"))
.flat_map(|(_, value)| value)
.map(|val| {
val.split(',').collect::<Vec<&str>>()[0]
.split('=')
.map(|split| split.to_string())
.collect::<Vec<String>>()
})
.map(|uid| EqFilter::from(uid[0].to_string(), uid[1].to_string()))
.for_each(|eq| or_filter.add(Box::new(eq)));
let result = self
.streaming_search(base_dn, scope, &or_filter, attributes, None, Vec::new())
.await;
let mut members = Vec::new();
match result {
Ok(result) => {
let mut stream = Box::pin(result);
while let Some(member) = stream.next().await {
match member {
Ok(member) => {
let user: T = member.to_record().unwrap();
members.push(user);
}
Err(err) => {
error!("Error getting member error {:?}", err);
}
}
}
return Ok(members);
}
Err(err) => {
error!("Error getting members {:?} error {:?}", group_dn, err);
}
}
Ok(members)
}
pub async fn remove_users_from_group(
&mut self,
group_dn: &str,
users: Vec<&str>,
) -> Result<(), Error> {
let mut mods = Vec::new();
let users = users.iter().copied().collect::<HashSet<&str>>();
mods.push(Mod::Delete("member", users));
let res = self.ldap.modify(group_dn, mods).await;
if let Err(err) = res {
return Err(Error::Update(
format!("Error removing users from group:{group_dn:?}: {err:?}"),
err,
));
}
let res = res.unwrap().success();
if let Err(err) = res {
match err {
LdapError::LdapResult { result } => {
if result.rc == NO_SUCH_RECORD {
return Err(Error::NotFound(format!(
"No records found for the uid: {group_dn:?}"
)));
}
}
_ => {
return Err(Error::Update(
format!("Error removing users from group:{group_dn:?}: {err:?}"),
err,
));
}
}
}
Ok(())
}
pub async fn get_associtated_groups(
&mut self,
group_ou: &str,
user_dn: &str,
) -> Result<Vec<String>, Error> {
let group_filter = Box::new(EqFilter::from(
"objectClass".to_string(),
"groupOfNames".to_string(),
));
let user_filter = Box::new(EqFilter::from("member".to_string(), user_dn.to_string()));
let mut filter = AndFilter::default();
filter.add(group_filter);
filter.add(user_filter);
let search = self
.ldap
.search(
group_ou,
Scope::Subtree,
filter.filter().as_str(),
vec!["cn"],
)
.await;
if let Err(error) = search {
return Err(Error::Query(
format!("Error searching for record: {error:?}"),
error,
));
}
let result = search.unwrap().success();
if let Err(error) = result {
return Err(Error::Query(
format!("Error searching for record: {error:?}"),
error,
));
}
let records = result.unwrap().0;
if records.is_empty() {
return Err(Error::NotFound(String::from(
"User does not belong to any groups",
)));
}
let record = records
.iter()
.map(|record| SearchEntry::construct(record.to_owned()))
.map(|se| se.attrs)
.flat_map(|att| {
att.get("cn")
.unwrap()
.iter()
.map(|x| x.to_owned())
.collect::<Vec<String>>()
})
.collect::<Vec<String>>();
Ok(record)
}
}
fn vec_to_option<T>(vec: Vec<T>) -> Option<Vec<T>> {
if vec.is_empty() { None } else { Some(vec) }
}
#[derive(Serialize)]
#[serde(remote = "ldap3::SearchEntry")]
struct Ldap3SearchEntry {
pub dn: String,
#[serde(flatten)]
pub attrs: HashMap<String, Vec<String>>,
#[serde(flatten)]
pub bin_attrs: HashMap<String, Vec<Vec<u8>>>,
}
#[derive(Serialize)]
#[serde(transparent)]
struct SerializeWrapper(#[serde(with = "Ldap3SearchEntry")] ldap3::SearchEntry);
#[instrument(level = Level::DEBUG)]
fn to_signle_value<T: for<'a> Deserialize<'a>>(search_entry: SearchEntry) -> Result<T, Error> {
let string_attributes = search_entry
.attrs
.into_iter()
.filter(|(_, value)| !value.is_empty())
.map(|(arrta, value)| {
if value.len() > 1 {
warn!("Treating multivalued attribute {arrta} as singlevalued.")
}
(Value::String(arrta), map_to_single_value(value.first()))
});
let binary_attributes = search_entry
.bin_attrs
.into_iter()
.filter(|(_, value)| !value.is_empty())
.map(|(arrta, value)| {
if value.len() > 1 {
warn!("Treating multivalued attribute {arrta} as singlevalued.")
}
(
Value::String(arrta),
map_to_single_value_bin(value.first().cloned()),
)
});
let dn_iter = iter::once(search_entry.dn)
.map(|dn| (Value::String(String::from("dn")), Value::String(dn)));
let all_fields = string_attributes
.chain(binary_attributes)
.chain(dn_iter)
.collect();
let value = serde_value::Value::Map(all_fields);
T::deserialize(value)
.map_err(|err| Error::Mapping(format!("Error converting search result to object, {err:?}")))
}
#[instrument(level = Level::DEBUG)]
fn to_value<T: for<'a> Deserialize<'a>>(search_entry: SearchEntry) -> Result<T, Error> {
let string_attributes = search_entry
.attrs
.into_iter()
.filter(|(_, value)| !value.is_empty())
.map(|(arrta, value)| {
if value.len() == 1 {
return (Value::String(arrta), map_to_single_value(value.first()));
}
(Value::String(arrta), map_to_multi_value(value))
});
let binary_attributes = search_entry
.bin_attrs
.into_iter()
.filter(|(_, value)| !value.is_empty())
.map(|(arrta, value)| {
if value.len() > 1 {
warn!("Treating multivalued attribute {arrta} as singlevalued.")
}
(
Value::String(arrta),
map_to_single_value_bin(value.first().cloned()),
)
});
let dn_iter = iter::once(search_entry.dn)
.map(|dn| (Value::String(String::from("dn")), Value::String(dn)));
let all_fields = string_attributes
.chain(binary_attributes)
.chain(dn_iter)
.collect();
let value = serde_value::Value::Map(all_fields);
T::deserialize(value)
.map_err(|err| Error::Mapping(format!("Error converting search result to object, {err:?}")))
}
fn map_to_multi_value(attra_value: Vec<String>) -> serde_value::Value {
serde_value::Value::Seq(
attra_value
.iter()
.map(|value| serde_value::Value::String(value.to_string()))
.collect(),
)
}
fn map_to_multi_value_bin(attra_values: Vec<Vec<u8>>) -> serde_value::Value {
let value_bytes = attra_values
.iter()
.map(|value| {
value
.iter()
.map(|byte| Value::U8(*byte))
.collect::<Vec<Value>>()
})
.map(serde_value::Value::Seq)
.collect::<Vec<Value>>();
serde_value::Value::Seq(value_bytes)
}
#[instrument(level = Level::DEBUG)]
fn to_multi_value<T: for<'a> Deserialize<'a>>(search_entry: SearchEntry) -> Result<T, Error> {
let value = serde_value::to_value(SerializeWrapper(search_entry)).map_err(|err| {
Error::Mapping(format!("Error converting search result to object, {err:?}"))
})?;
T::deserialize(value)
.map_err(|err| Error::Mapping(format!("Error converting search result to object, {err:?}")))
}
fn map_to_single_value(attra_value: Option<&String>) -> serde_value::Value {
match attra_value {
Some(value) => serde_value::Value::String(value.to_string()),
None => serde_value::Value::Option(Option::None),
}
}
fn map_to_single_value_bin(attra_values: Option<Vec<u8>>) -> serde_value::Value {
match attra_values {
Some(bytes) => {
let value_bytes = bytes.into_iter().map(Value::U8).collect();
serde_value::Value::Seq(value_bytes)
}
None => serde_value::Value::Option(Option::None),
}
}
struct StreamDropWrapper<'a, S, A>
where
S: AsRef<str> + Send + Sync + 'a,
A: AsRef<[S]> + Send + Sync + 'a,
{
pub search_stream: SearchStream<'a, S, A>,
}
impl<'a, S, A> Drop for StreamDropWrapper<'a, S, A>
where
S: AsRef<str> + Send + Sync + 'a,
A: AsRef<[S]> + Send + Sync + 'a,
{
fn drop(&mut self) {
block_on(self.cleanup());
}
}
impl<'a, S, A> StreamDropWrapper<'a, S, A>
where
S: AsRef<str> + Send + Sync + 'a,
A: AsRef<[S]> + Send + Sync + 'a,
{
#[instrument(level = Level::TRACE, skip_all)]
async fn cleanup(&mut self) -> () {
let finish_result = self.search_stream.finish().await;
match finish_result.success() {
Ok(_) => (), Err(LdapError::LdapResult {
result: LdapResult { rc: 88, .. },
}) => (),
Err(finish_err) => error!("The stream finished with an error: {finish_err}"),
}
match self.search_stream.state() {
StreamState::Done | StreamState::Closed => (),
StreamState::Error => {
error!(
"Stream is in Error state. Not trying to cancel it as it could do more harm than good."
);
}
StreamState::Fresh | StreamState::Active => {
info!("Stream is still open. Issuing cancellation to the server.");
let msgid = self.search_stream.ldap_handle().last_id();
let result = self.search_stream.ldap_handle().abandon(msgid).await;
match result {
Ok(_) => (),
Err(err) => {
error!("Error abandoning search result: {:?}", err);
()
}
}
}
}
}
}
fn to_native_stream<'a, S, A>(
ldap3_stream: SearchStream<'a, S, A>,
) -> Result<impl Stream<Item = Result<Record, Error>> + 'a + use<'a, S, A>, Error>
where
S: AsRef<str> + Send + Sync + 'a,
A: AsRef<[S]> + Send + Sync + 'a,
{
let stream_wrapper = StreamDropWrapper {
search_stream: ldap3_stream,
};
let stream = stream::try_unfold(stream_wrapper, async |mut search| {
match search.search_stream.next().await {
Ok(Some(result_entry)) => Ok(Some((
Record {
search_entry: SearchEntry::construct(result_entry),
},
search,
))),
Ok(None) => Ok(None),
Err(ldap_error) => Err(Error::Query(
format!("Error getting next record: {ldap_error:?}"),
ldap_error,
)),
}
});
Ok(stream)
}
pub struct Record {
search_entry: SearchEntry,
}
impl Record {
pub fn to_record<T: for<'b> serde::Deserialize<'b>>(self) -> Result<T, Error> {
to_value(self.search_entry)
}
#[deprecated(
since = "6.0.0",
note = "Use to_record instead. This method is deprecated and will be removed in future versions."
)]
pub fn to_multi_valued_record_<T: for<'b> serde::Deserialize<'b>>(self) -> Result<T, Error> {
to_multi_value(self.search_entry)
}
}
pub enum StreamResult<T> {
Record(T),
Done,
Finished,
}
#[derive(Debug, Error)]
pub enum Error {
#[error("{0}")]
Query(String, #[source] LdapError),
#[error("{0}")]
NotFound(String),
#[error("{0}")]
MultipleResults(String),
#[error("{0}")]
AuthenticationFailed(String),
#[error("{0}")]
Create(String, #[source] LdapError),
#[error("{0}")]
Update(String, #[source] LdapError),
#[error("{0}")]
Delete(String, #[source] LdapError),
#[error("{0}")]
Mapping(String),
#[error("{0}")]
Connection(String, #[source] LdapError),
#[error("{0}")]
Close(String, #[source] LdapError),
#[error("{0}")]
Abandon(String, #[source] LdapError),
#[error("{0}")]
Sort(String),
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::anyhow;
use serde::Deserialize;
use serde_with::OneOrMany;
use serde_with::serde_as;
use uuid::Uuid;
#[test]
fn create_multi_value_test() {
let mut map: HashMap<String, Vec<String>> = HashMap::new();
map.insert(
"key1".to_string(),
vec!["value1".to_string(), "value2".to_string()],
);
map.insert(
"key2".to_string(),
vec!["value3".to_string(), "value4".to_string()],
);
let dn = "CN=Thing,OU=Unit,DC=example,DC=org";
let entry = SearchEntry {
dn: dn.to_string(),
attrs: map,
bin_attrs: HashMap::new(),
};
let test = to_multi_value::<TestMultiValued>(entry);
let test = test.unwrap();
assert_eq!(test.key1, vec!["value1".to_string(), "value2".to_string()]);
assert_eq!(test.key2, vec!["value3".to_string(), "value4".to_string()]);
assert_eq!(test.dn, dn);
}
#[test]
fn create_single_value_test() {
let mut map: HashMap<String, Vec<String>> = HashMap::new();
map.insert("key1".to_string(), vec!["value1".to_string()]);
map.insert("key2".to_string(), vec!["value2".to_string()]);
map.insert("key4".to_string(), vec!["value4".to_string()]);
let dn = "CN=Thing,OU=Unit,DC=example,DC=org";
let entry = SearchEntry {
dn: dn.to_string(),
attrs: map,
bin_attrs: HashMap::new(),
};
let test = to_signle_value::<TestSingleValued>(entry);
let test = test.unwrap();
assert_eq!(test.key1, "value1".to_string());
assert_eq!(test.key2, "value2".to_string());
assert!(test.key3.is_none());
assert_eq!(test.key4.unwrap(), "value4".to_string());
assert_eq!(test.dn, dn);
}
#[test]
fn create_to_value_string_test() {
let mut map: HashMap<String, Vec<String>> = HashMap::new();
map.insert("key1".to_string(), vec!["value1".to_string()]);
map.insert("key2".to_string(), vec!["value2".to_string()]);
map.insert("key4".to_string(), vec!["value4".to_string()]);
map.insert(
"key5".to_string(),
vec!["value5".to_string(), "value6".to_string()],
);
let dn = "CN=Thing,OU=Unit,DC=example,DC=org";
let entry = SearchEntry {
dn: dn.to_string(),
attrs: map,
bin_attrs: HashMap::new(),
};
let test = to_value::<TestValued>(entry);
let test = test.unwrap();
assert_eq!(test.key1, "value1".to_string());
assert!(test.key3.is_none());
let key4 = test.key4;
assert_eq!(key4[0], "value4".to_string());
let key5 = test.key5;
assert_eq!(key5[0], "value5".to_string());
assert_eq!(key5[1], "value6".to_string());
assert_eq!(test.dn, dn);
}
#[test]
fn binary_single_to_value_test() -> anyhow::Result<()> {
#[derive(Deserialize)]
struct TestMultivalueBinary {
pub uuids: Uuid,
pub key1: String,
}
let (bytes, correct_string_representation) = get_binary_uuid();
let entry = SearchEntry {
dn: String::from("CN=Thing,OU=Unit,DC=example,DC=org"),
attrs: HashMap::from([(String::from("key1"), vec![String::from("value1")])]),
bin_attrs: HashMap::from([(String::from("uuids"), vec![bytes])]),
};
let test = to_value::<TestMultivalueBinary>(entry).unwrap();
let string_uuid = test.uuids.hyphenated().to_string();
assert_eq!(string_uuid, correct_string_representation);
Ok(())
}
fn binary_multi_to_value_test() -> anyhow::Result<()> {
#[serde_as]
#[derive(Deserialize)]
struct TestMultivalueBinary {
#[serde_as(as = "OneOrMany<_>")]
pub uuids: Vec<Uuid>,
pub key1: String,
}
let (bytes, correct_string_representation) = get_binary_uuid();
let entry = SearchEntry {
dn: String::from("CN=Thing,OU=Unit,DC=example,DC=org"),
attrs: HashMap::from([(String::from("key1"), vec![String::from("value1")])]),
bin_attrs: HashMap::from([(String::from("uuids"), vec![bytes])]),
};
let test = to_value::<TestMultivalueBinary>(entry).unwrap();
match test.uuids.as_slice() {
[one] => {
let string_uuid = one.hyphenated().to_string();
assert_eq!(string_uuid, correct_string_representation);
Ok(())
}
[..] => Err(anyhow!("There was supposed to be exactly one uuid.")),
}
}
#[derive(Debug, Deserialize)]
struct TestMultiValued {
dn: String,
key1: Vec<String>,
key2: Vec<String>,
}
#[derive(Debug, Deserialize)]
struct TestSingleValued {
dn: String,
key1: String,
key2: String,
key3: Option<String>,
key4: Option<String>,
}
#[serde_as]
#[derive(Debug, Deserialize)]
struct TestValued {
dn: String,
key1: String,
key3: Option<String>,
#[serde_as(as = "OneOrMany<_>")]
key4: Vec<String>,
#[serde_as(as = "OneOrMany<_>")]
key5: Vec<String>,
}
fn get_binary_uuid() -> (Vec<u8>, String) {
let bytes = vec![
0xa1, 0xa2, 0xa3, 0xa4, 0xb1, 0xb2, 0xc1, 0xc2, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6,
0xd7, 0xd8,
];
let correct_string_representation = String::from("a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8");
(bytes, correct_string_representation)
}
#[test]
fn deserialize_binary_multi_value_test() -> anyhow::Result<()> {
#[derive(Deserialize)]
struct TestMultivalueBinary {
pub uuids: Vec<Uuid>,
}
let (bytes, correct_string_representation) = get_binary_uuid();
let entry = SearchEntry {
dn: String::from("CN=Thing,OU=Unit,DC=example,DC=org"),
attrs: HashMap::new(),
bin_attrs: HashMap::from([(String::from("uuids"), vec![bytes])]),
};
let record = Record {
search_entry: entry,
};
let deserialized: TestMultivalueBinary = record.to_multi_valued_record_()?;
match deserialized.uuids.as_slice() {
[one] => {
let string_uuid = one.hyphenated().to_string();
assert_eq!(string_uuid, correct_string_representation);
Ok(())
}
[..] => Err(anyhow!("There was supposed to be exactly one uuid.")),
}
}
#[test]
fn deserialize_binary_single_value_test() -> anyhow::Result<()> {
#[derive(Deserialize)]
struct TestSingleValueBinary {
pub uuid: Uuid,
}
let (bytes, correct_string_representation) = get_binary_uuid();
let entry = SearchEntry {
dn: String::from("CN=Thing,OU=Unit,DC=example,DC=org"),
attrs: HashMap::new(),
bin_attrs: HashMap::from([(String::from("uuid"), vec![bytes])]),
};
let record = Record {
search_entry: entry,
};
let deserialized: TestSingleValueBinary = record.to_record()?;
let string_uuid = deserialized.uuid.hyphenated().to_string();
assert_eq!(string_uuid, correct_string_representation);
Ok(())
}
}
#[doc = include_str!("../README.md")]
#[cfg(doctest)]
pub struct ReadmeDoctests;