Add attribute list handling

Also, fix various clippy warnings
This commit is contained in:
Valentin Tolmer 2021-03-22 09:59:58 +01:00
parent cda2bcacc3
commit 31e8998ac3
4 changed files with 81 additions and 69 deletions

View File

@ -11,7 +11,6 @@ pub struct BindRequest {
#[cfg_attr(test, derive(PartialEq, Eq, Debug))] #[cfg_attr(test, derive(PartialEq, Eq, Debug))]
pub struct ListUsersRequest { pub struct ListUsersRequest {
// filters // filters
pub attrs: Vec<String>,
} }
#[cfg_attr(test, derive(PartialEq, Eq, Debug))] #[cfg_attr(test, derive(PartialEq, Eq, Debug))]

View File

@ -2,17 +2,15 @@ use crate::domain::handler::{BackendHandler, ListUsersRequest, User};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use ldap3_server::simple::*; use ldap3_server::simple::*;
fn make_dn_pair<'a, I>(mut iter: I) -> Result<(String, String)> fn make_dn_pair<I>(mut iter: I) -> Result<(String, String)>
where where
I: Iterator<Item = String>, I: Iterator<Item = String>,
{ {
let pair = ( let pair = (
iter.next() iter.next()
.ok_or(anyhow::Error::msg("Empty DN element"))? .ok_or_else(|| anyhow::Error::msg("Empty DN element"))?,
.clone(),
iter.next() iter.next()
.ok_or(anyhow::Error::msg("Missing DN value"))? .ok_or_else(|| anyhow::Error::msg("Missing DN value"))?,
.clone(),
); );
if let Some(e) = iter.next() { if let Some(e) = iter.next() {
bail!( bail!(
@ -26,48 +24,47 @@ where
} }
fn parse_distinguished_name(dn: &str) -> Result<Vec<(String, String)>> { fn parse_distinguished_name(dn: &str) -> Result<Vec<(String, String)>> {
dn.split(",") dn.split(',')
.map(|s| make_dn_pair(s.split("=").map(String::from))) .map(|s| make_dn_pair(s.split('=').map(String::from)))
.collect() .collect()
} }
fn make_ldap_search_result_entry(user: User, base_dn_str: &str) -> LdapSearchResultEntry { fn get_attribute(user: &User, attribute: &str) -> Result<Vec<String>> {
LdapSearchResultEntry { match attribute {
dn: format!("cn={},{}", user.user_id, base_dn_str), "objectClass" => Ok(vec![
attributes: vec![ "inetOrgPerson".to_string(),
LdapPartialAttribute { "posixAccount".to_string(),
atype: "objectClass".to_string(), "mailAccount".to_string(),
vals: vec![ ]),
"inetOrgPerson".to_string(), "uid" => Ok(vec![user.user_id.to_string()]),
"posixAccount".to_string(), "mail" => Ok(vec![user.email.to_string()]),
"mailAccount".to_string(), "givenName" => Ok(vec![user.first_name.to_string()]),
], "sn" => Ok(vec![user.last_name.to_string()]),
}, "cn" => Ok(vec![user.display_name.to_string()]),
LdapPartialAttribute { _ => bail!("Unsupported attribute: {}", attribute),
atype: "uid".to_string(),
vals: vec![user.user_id],
},
LdapPartialAttribute {
atype: "mail".to_string(),
vals: vec![user.email],
},
LdapPartialAttribute {
atype: "givenName".to_string(),
vals: vec![user.first_name],
},
LdapPartialAttribute {
atype: "sn".to_string(),
vals: vec![user.last_name],
},
LdapPartialAttribute {
atype: "cn".to_string(),
vals: vec![user.display_name],
},
],
} }
} }
fn is_subtree(subtree: &Vec<(String, String)>, base_tree: &Vec<(String, String)>) -> bool { fn make_ldap_search_result_entry(
user: User,
base_dn_str: &str,
attributes: &[String],
) -> Result<LdapSearchResultEntry> {
Ok(LdapSearchResultEntry {
dn: format!("cn={},{}", user.user_id, base_dn_str),
attributes: attributes
.iter()
.map(|a| {
Ok(LdapPartialAttribute {
atype: a.to_string(),
vals: get_attribute(&user, a)?,
})
})
.collect::<Result<Vec<LdapPartialAttribute>>>()?,
})
}
fn is_subtree(subtree: &[(String, String)], base_tree: &[(String, String)]) -> bool {
if subtree.len() < base_tree.len() { if subtree.len() < base_tree.len() {
return false; return false;
} }
@ -92,10 +89,12 @@ impl<Backend: BackendHandler> LdapHandler<Backend> {
Self { Self {
dn: "Unauthenticated".to_string(), dn: "Unauthenticated".to_string(),
backend_handler, backend_handler,
base_dn: parse_distinguished_name(&ldap_base_dn).expect(&format!( base_dn: parse_distinguished_name(&ldap_base_dn).unwrap_or_else(|_| {
"Invalid value for ldap_base_dn in configuration: {}", panic!(
ldap_base_dn "Invalid value for ldap_base_dn in configuration: {}",
)), ldap_base_dn
)
}),
base_dn_str: ldap_base_dn, base_dn_str: ldap_base_dn,
} }
} }
@ -126,11 +125,10 @@ impl<Backend: BackendHandler> LdapHandler<Backend> {
} }
}; };
if !is_subtree(&dn_parts, &self.base_dn) { if !is_subtree(&dn_parts, &self.base_dn) {
// Search path is not in our tree, just return an empty success.
return vec![lsr.gen_success()]; return vec![lsr.gen_success()];
} }
let users = match self.backend_handler.list_users(ListUsersRequest { let users = match self.backend_handler.list_users(ListUsersRequest {}) {
attrs: lsr.attrs.clone(),
}) {
Ok(users) => users, Ok(users) => users,
Err(e) => { Err(e) => {
return vec![lsr.gen_error( return vec![lsr.gen_error(
@ -139,12 +137,15 @@ impl<Backend: BackendHandler> LdapHandler<Backend> {
)] )]
} }
}; };
let mut res = users
users
.into_iter() .into_iter()
.map(|u| lsr.gen_result_entry(make_ldap_search_result_entry(u, &self.base_dn_str))) .map(|u| make_ldap_search_result_entry(u, &self.base_dn_str, &lsr.attrs))
.collect::<Vec<_>>(); .map(|entry| Ok(lsr.gen_result_entry(entry?)))
res.push(lsr.gen_success()); // If the processing succeeds, add a success message at the end.
res .chain(std::iter::once(Ok(lsr.gen_success())))
.collect::<Result<Vec<_>>>()
.unwrap_or_else(|e| vec![lsr.gen_error(LdapResultCode::NoSuchAttribute, e.to_string())])
} }
pub fn do_whoami(&mut self, wr: &WhoamiRequest) -> LdapMsg { pub fn do_whoami(&mut self, wr: &WhoamiRequest) -> LdapMsg {
@ -210,22 +211,22 @@ mod tests {
#[test] #[test]
fn test_is_subtree() { fn test_is_subtree() {
let subtree1 = vec![ let subtree1 = &[
("ou".to_string(), "people".to_string()), ("ou".to_string(), "people".to_string()),
("dc".to_string(), "example".to_string()), ("dc".to_string(), "example".to_string()),
("dc".to_string(), "com".to_string()), ("dc".to_string(), "com".to_string()),
]; ];
let root = vec![ let root = &[
("dc".to_string(), "example".to_string()), ("dc".to_string(), "example".to_string()),
("dc".to_string(), "com".to_string()), ("dc".to_string(), "com".to_string()),
]; ];
assert!(is_subtree(&subtree1, &root)); assert!(is_subtree(subtree1, root));
assert!(!is_subtree(&vec![], &root)); assert!(!is_subtree(&[], root));
} }
#[test] #[test]
fn test_parse_distinguished_name() { fn test_parse_distinguished_name() {
let parsed_dn = vec![ let parsed_dn = &[
("ou".to_string(), "people".to_string()), ("ou".to_string(), "people".to_string()),
("dc".to_string(), "example".to_string()), ("dc".to_string(), "example".to_string()),
("dc".to_string(), "com".to_string()), ("dc".to_string(), "com".to_string()),
@ -241,7 +242,7 @@ mod tests {
let mut mock = MockTestBackendHandler::new(); let mut mock = MockTestBackendHandler::new();
mock.expect_bind().return_once(|_| Ok(())); mock.expect_bind().return_once(|_| Ok(()));
mock.expect_list_users() mock.expect_list_users()
.with(eq(ListUsersRequest { attrs: vec![] })) .with(eq(ListUsersRequest {}))
.times(1) .times(1)
.return_once(|_| { .return_once(|_| {
Ok(vec![ Ok(vec![
@ -275,7 +276,14 @@ mod tests {
base: "ou=people,dc=example,dc=com".to_string(), base: "ou=people,dc=example,dc=com".to_string(),
scope: LdapSearchScope::Base, scope: LdapSearchScope::Base,
filter: LdapFilter::And(vec![]), filter: LdapFilter::And(vec![]),
attrs: vec![], attrs: vec![
"objectClass".to_string(),
"uid".to_string(),
"mail".to_string(),
"givenName".to_string(),
"sn".to_string(),
"cn".to_string(),
],
}; };
assert_eq!( assert_eq!(
ldap_handler.do_search(&request), ldap_handler.do_search(&request),
@ -285,7 +293,11 @@ mod tests {
attributes: vec![ attributes: vec![
LdapPartialAttribute { LdapPartialAttribute {
atype: "objectClass".to_string(), atype: "objectClass".to_string(),
vals: vec!["inetOrgPerson".to_string(), "posixAccount".to_string(), "mailAccount".to_string()] vals: vec![
"inetOrgPerson".to_string(),
"posixAccount".to_string(),
"mailAccount".to_string()
]
}, },
LdapPartialAttribute { LdapPartialAttribute {
atype: "uid".to_string(), atype: "uid".to_string(),
@ -314,7 +326,11 @@ mod tests {
attributes: vec![ attributes: vec![
LdapPartialAttribute { LdapPartialAttribute {
atype: "objectClass".to_string(), atype: "objectClass".to_string(),
vals: vec!["inetOrgPerson".to_string(), "posixAccount".to_string(), "mailAccount".to_string()] vals: vec![
"inetOrgPerson".to_string(),
"posixAccount".to_string(),
"mailAccount".to_string()
]
}, },
LdapPartialAttribute { LdapPartialAttribute {
atype: "uid".to_string(), atype: "uid".to_string(),

View File

@ -20,10 +20,7 @@ async fn handle_incoming_message<Backend: BackendHandler>(
) -> Result<bool> { ) -> Result<bool> {
use futures_util::SinkExt; use futures_util::SinkExt;
use std::convert::TryFrom; use std::convert::TryFrom;
let server_op = match msg let server_op = match msg.map_err(|_e| ()).and_then(ServerOps::try_from) {
.map_err(|_e| ())
.and_then(|msg| ServerOps::try_from(msg))
{
Ok(a_value) => a_value, Ok(a_value) => a_value,
Err(an_error) => { Err(an_error) => {
let _err = resp let _err = resp

View File

@ -23,7 +23,7 @@ where
let count = Arc::new(AtomicUsize::new(0)); let count = Arc::new(AtomicUsize::new(0));
Ok(server_builder server_builder
.bind("http", ("0.0.0.0", config.http_port), move || { .bind("http", ("0.0.0.0", config.http_port), move || {
let count = Arc::clone(&count); let count = Arc::clone(&count);
let num2 = Arc::clone(&count); let num2 = Arc::clone(&count);
@ -73,5 +73,5 @@ where
"While bringing up the TCP server with port {}", "While bringing up the TCP server with port {}",
config.http_port config.http_port
) )
})?) })
} }