ldap: Implement group listing, fix various bugs

This commit is contained in:
Valentin Tolmer 2021-10-24 01:01:35 +09:00 committed by nitnelave
parent 5a00b7d8bb
commit 107c8ec96e
6 changed files with 236 additions and 68 deletions

1
Cargo.lock generated
View File

@ -1740,6 +1740,7 @@ dependencies = [
"thiserror", "thiserror",
"time 0.2.27", "time 0.2.27",
"tokio", "tokio",
"tokio-stream",
"tokio-util", "tokio-util",
"tracing", "tracing",
"tracing-actix-web", "tracing-actix-web",

View File

@ -24,6 +24,7 @@ RUN set -x \
# Prepare the dependency list. # Prepare the dependency list.
FROM chef AS planner FROM chef AS planner
COPY . . COPY . .
USER root
RUN cargo chef prepare --recipe-path recipe.json RUN cargo chef prepare --recipe-path recipe.json
# Build dependencies # Build dependencies

View File

@ -38,10 +38,11 @@
## Admin password. ## Admin password.
## Password for the admin account, both for the LDAP bind and for the ## Password for the admin account, both for the LDAP bind and for the
## administration interface. ## administration interface. It is only used when initially creating
## the admin user.
## It should be minimum 8 characters long. ## It should be minimum 8 characters long.
## You can set it with the LLDAP_LDAP_USER_PASS environment variable. ## You can set it with the LLDAP_LDAP_USER_PASS environment variable.
## Note: you can create another admin user for LDAP/administration, this ## Note: you can create another admin user for user administration, this
## is just the default one. ## is just the default one.
#ldap_user_pass = "REPLACE_WITH_PASSWORD" #ldap_user_pass = "REPLACE_WITH_PASSWORD"

View File

@ -38,6 +38,7 @@ thiserror = "*"
time = "0.2" time = "0.2"
tokio = { version = "1.2.0", features = ["full"] } tokio = { version = "1.2.0", features = ["full"] }
tokio-util = "0.6.3" tokio-util = "0.6.3"
tokio-stream = "*"
tracing = "*" tracing = "*"
tracing-actix-web = "0.4.0-beta.7" tracing-actix-web = "0.4.0-beta.7"
tracing-log = "*" tracing-log = "*"

View File

@ -1,6 +1,11 @@
use crate::domain::handler::{BackendHandler, LoginHandler, RequestFilter, User}; use crate::domain::handler::{
BackendHandler, Group, GroupIdAndName, LoginHandler, RequestFilter, User,
};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use futures::stream::StreamExt;
use futures_util::TryStreamExt;
use ldap3_server::simple::*; use ldap3_server::simple::*;
use log::*;
fn make_dn_pair<I>(mut iter: I) -> Result<(String, String)> fn make_dn_pair<I>(mut iter: I) -> Result<(String, String)>
where where
@ -41,14 +46,16 @@ fn get_group_id_from_distinguished_name(
if parts.len() == base_tree.len() + 2 { if parts.len() == base_tree.len() + 2 {
if parts[1].0 != "ou" || parts[1].1 != "groups" || parts[0].0 != "cn" { if parts[1].0 != "ou" || parts[1].1 != "groups" || parts[0].0 != "cn" {
bail!( bail!(
r#"Unexpected group DN format. Expected: "cn=groupname,ou=groups,{}""#, r#"Unexpected group DN format. Got "{}", expected: "cn=groupname,ou=groups,{}""#,
dn,
base_dn_str base_dn_str
); );
} }
Ok(parts[0].1.to_string()) Ok(parts[0].1.to_string())
} else { } else {
bail!( bail!(
r#"Unexpected group DN format. Expected: "cn=groupname,ou=groups,{}""#, r#"Unexpected group DN format. Got "{}", expected: "cn=groupname,ou=groups,{}""#,
dn,
base_dn_str base_dn_str
); );
} }
@ -58,62 +65,94 @@ fn get_user_id_from_distinguished_name(
dn: &str, dn: &str,
base_tree: &[(String, String)], base_tree: &[(String, String)],
base_dn_str: &str, base_dn_str: &str,
ldap_user_dn: &str,
) -> Result<String> { ) -> Result<String> {
let parts = parse_distinguished_name(dn)?; let parts = parse_distinguished_name(dn)?;
if !is_subtree(&parts, base_tree) { if !is_subtree(&parts, base_tree) {
bail!("Not a subtree of the base tree"); bail!("Not a subtree of the base tree");
} }
if parts.len() == base_tree.len() + 1 { if parts.len() == base_tree.len() + 2 {
if dn != ldap_user_dn {
bail!(r#"Wrong admin DN. Expected: "{}""#, ldap_user_dn);
}
Ok(parts[0].1.to_string())
} else if parts.len() == base_tree.len() + 2 {
if parts[1].0 != "ou" || parts[1].1 != "people" || parts[0].0 != "cn" { if parts[1].0 != "ou" || parts[1].1 != "people" || parts[0].0 != "cn" {
bail!( bail!(
r#"Unexpected user DN format. Expected: "cn=username,ou=people,{}""#, r#"Unexpected user DN format. Got "{}", expected: "cn=username,ou=people,{}""#,
dn,
base_dn_str base_dn_str
); );
} }
Ok(parts[0].1.to_string()) Ok(parts[0].1.to_string())
} else { } else {
bail!( bail!(
r#"Unexpected user DN format. Expected: "cn=username,ou=people,{}""#, r#"Unexpected user DN format. Got "{}", expected: "cn=username,ou=people,{}""#,
dn,
base_dn_str base_dn_str
); );
} }
} }
fn get_attribute(user: &User, attribute: &str) -> Result<Vec<String>> { fn get_user_attribute(user: &User, attribute: &str) -> Result<Vec<String>> {
match attribute { match attribute {
"objectClass" => Ok(vec![ "objectClass" => Ok(vec![
"inetOrgPerson".to_string(), "inetOrgPerson".to_string(),
"posixAccount".to_string(), "posixAccount".to_string(),
"mailAccount".to_string(), "mailAccount".to_string(),
"person".to_string(),
]), ]),
"uid" => Ok(vec![user.user_id.clone()]), "uid" => Ok(vec![user.user_id.clone()]),
"mail" => Ok(vec![user.email.clone()]), "mail" => Ok(vec![user.email.clone()]),
"givenName" => Ok(vec![user.first_name.clone()]), "givenName" => Ok(vec![user.first_name.clone()]),
"sn" => Ok(vec![user.last_name.clone()]), "sn" => Ok(vec![user.last_name.clone()]),
"cn" => Ok(vec![user.display_name.clone()]), "cn" => Ok(vec![user.display_name.clone()]),
_ => bail!("Unsupported attribute: {}", attribute), "displayName" => Ok(vec![user.display_name.clone()]),
"supportedExtension" => Ok(vec![]),
_ => bail!("Unsupported user attribute: {}", attribute),
} }
} }
fn make_ldap_search_result_entry( fn make_ldap_search_user_result_entry(
user: User, user: User,
base_dn_str: &str, base_dn_str: &str,
attributes: &[String], attributes: &[String],
) -> Result<LdapSearchResultEntry> { ) -> Result<LdapSearchResultEntry> {
Ok(LdapSearchResultEntry { Ok(LdapSearchResultEntry {
dn: format!("cn={},{}", user.user_id, base_dn_str), dn: format!("cn={},ou=people,{}", user.user_id, base_dn_str),
attributes: attributes attributes: attributes
.iter() .iter()
.map(|a| { .map(|a| {
Ok(LdapPartialAttribute { Ok(LdapPartialAttribute {
atype: a.to_string(), atype: a.to_string(),
vals: get_attribute(&user, a)?, vals: get_user_attribute(&user, a)?,
})
})
.collect::<Result<Vec<LdapPartialAttribute>>>()?,
})
}
fn get_group_attribute(group: &Group, base_dn_str: &str, attribute: &str) -> Result<Vec<String>> {
match attribute {
"objectClass" => Ok(vec!["groupOfUniqueNames".to_string()]),
"cn" => Ok(vec![group.display_name.clone()]),
"uniqueMember" => Ok(group
.users
.iter()
.map(|u| format!("cn={},ou=people,{}", u, base_dn_str))
.collect()),
"supportedExtension" => Ok(vec![]),
_ => bail!("Unsupported group attribute: {}", attribute),
}
}
fn make_ldap_search_group_result_entry(
group: Group,
base_dn_str: &str,
attributes: &[String],
) -> Result<LdapSearchResultEntry> {
Ok(LdapSearchResultEntry {
dn: format!("cn={},ou=groups,{}", group.display_name, base_dn_str),
attributes: attributes
.iter()
.map(|a| {
Ok(LdapPartialAttribute {
atype: a.to_string(),
vals: get_group_attribute(&group, base_dn_str, a)?,
}) })
}) })
.collect::<Result<Vec<LdapPartialAttribute>>>()?, .collect::<Result<Vec<LdapPartialAttribute>>>()?,
@ -138,7 +177,7 @@ fn map_field(field: &str) -> Result<String> {
"user_id".to_string() "user_id".to_string()
} else if field == "mail" { } else if field == "mail" {
"email".to_string() "email".to_string()
} else if field == "cn" { } else if field == "cn" || field == "displayName" {
"display_name".to_string() "display_name".to_string()
} else if field == "givenName" { } else if field == "givenName" {
"first_name".to_string() "first_name".to_string()
@ -172,21 +211,18 @@ impl<Backend: BackendHandler + LoginHandler> LdapHandler<Backend> {
ldap_base_dn ldap_base_dn
) )
}), }),
ldap_user_dn: format!("cn={},{}", ldap_user_dn, &ldap_base_dn), ldap_user_dn: format!("cn={},ou=people,{}", ldap_user_dn, &ldap_base_dn),
base_dn_str: ldap_base_dn, base_dn_str: ldap_base_dn,
} }
} }
pub async fn do_bind(&mut self, sbr: &SimpleBindRequest) -> LdapMsg { pub async fn do_bind(&mut self, sbr: &SimpleBindRequest) -> LdapMsg {
let user_id = match get_user_id_from_distinguished_name( info!(r#"Received bind request for "{}""#, &sbr.dn);
&sbr.dn, let user_id =
&self.base_dn, match get_user_id_from_distinguished_name(&sbr.dn, &self.base_dn, &self.base_dn_str) {
&self.base_dn_str, Ok(s) => s,
&self.ldap_user_dn, Err(e) => return sbr.gen_error(LdapResultCode::NamingViolation, e.to_string()),
) { };
Ok(s) => s,
Err(e) => return sbr.gen_error(LdapResultCode::NamingViolation, e.to_string()),
};
match self match self
.backend_handler .backend_handler
.bind(crate::domain::handler::BindRequest { .bind(crate::domain::handler::BindRequest {
@ -204,31 +240,55 @@ impl<Backend: BackendHandler + LoginHandler> LdapHandler<Backend> {
} }
pub async fn do_search(&mut self, lsr: &SearchRequest) -> Vec<LdapMsg> { pub async fn do_search(&mut self, lsr: &SearchRequest) -> Vec<LdapMsg> {
info!("Received search request with filters: {:?}", &lsr.filter);
if self.dn != self.ldap_user_dn { if self.dn != self.ldap_user_dn {
return vec![lsr.gen_error( return vec![lsr.gen_error(
LdapResultCode::InsufficentAccessRights, LdapResultCode::InsufficentAccessRights,
r#"Current user is not allowed to query LDAP"#.to_string(), format!(
r#"Current user `{}` is not allowed to query LDAP, expected {}"#,
&self.dn, &self.ldap_user_dn
),
)]; )];
} }
let dn_parts = match parse_distinguished_name(&lsr.base) { let dn_parts = if lsr.base.is_empty() {
Ok(dn) => dn, self.base_dn.clone()
Err(_) => { } else {
return vec![lsr.gen_error( match parse_distinguished_name(&lsr.base) {
LdapResultCode::OperationsError, Ok(dn) => dn,
format!(r#"Could not parse base DN: "{}""#, lsr.base), Err(_) => {
)] return vec![lsr.gen_error(
LdapResultCode::OperationsError,
format!(r#"Could not parse base DN: "{}""#, lsr.base),
)]
}
} }
}; };
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. // Search path is not in our tree, just return an empty success.
return vec![lsr.gen_success()]; return vec![lsr.gen_success()];
} }
let filters = match self.convert_filter(&lsr.filter) { let mut results = Vec::new();
if dn_parts.len() == self.base_dn.len()
|| (dn_parts.len() == self.base_dn.len() + 1
&& dn_parts[0] == ("ou".to_string(), "people".to_string()))
{
results.extend(self.get_user_list(lsr).await);
}
if dn_parts.len() == self.base_dn.len() + 1
&& dn_parts[0] == ("ou".to_string(), "groups".to_string())
{
results.extend(self.get_groups_list(lsr).await);
}
results
}
async fn get_user_list(&self, lsr: &SearchRequest) -> Vec<LdapMsg> {
let filters = match self.convert_user_filter(&lsr.filter) {
Ok(f) => Some(f), Ok(f) => Some(f),
Err(e) => { Err(e) => {
return vec![lsr.gen_error( return vec![lsr.gen_error(
LdapResultCode::UnwillingToPerform, LdapResultCode::UnwillingToPerform,
format!("Unsupported filter: {}", e), format!("Unsupported user filter: {}", e),
)] )]
} }
}; };
@ -237,14 +297,84 @@ impl<Backend: BackendHandler + LoginHandler> LdapHandler<Backend> {
Err(e) => { Err(e) => {
return vec![lsr.gen_error( return vec![lsr.gen_error(
LdapResultCode::Other, LdapResultCode::Other,
format!(r#"Error during search for "{}": {}"#, lsr.base, e), format!(r#"Error during searching user "{}": {}"#, lsr.base, e),
)] )]
} }
}; };
users users
.into_iter() .into_iter()
.map(|u| make_ldap_search_result_entry(u, &self.base_dn_str, &lsr.attrs)) .map(|u| make_ldap_search_user_result_entry(u, &self.base_dn_str, &lsr.attrs))
.map(|entry| Ok(lsr.gen_result_entry(entry?)))
// If the processing succeeds, add a success message at the end.
.chain(std::iter::once(Ok(lsr.gen_success())))
.collect::<Result<Vec<_>>>()
.unwrap_or_else(|e| vec![lsr.gen_error(LdapResultCode::NoSuchAttribute, e.to_string())])
}
async fn get_groups_list(&self, lsr: &SearchRequest) -> Vec<LdapMsg> {
let for_user = match self.get_group_filter(&lsr.filter) {
Ok(u) => u,
Err(e) => {
return vec![lsr.gen_error(
LdapResultCode::UnwillingToPerform,
format!("Unsupported group filter: {}", e),
)]
}
};
async fn get_users_for_group<Backend: BackendHandler>(
backend_handler: &Backend,
g: &GroupIdAndName,
) -> Result<Group> {
let users = backend_handler
.list_users(Some(RequestFilter::MemberOfId(g.0)))
.await?;
Ok(Group {
id: g.0,
display_name: g.1.clone(),
users: users.into_iter().map(|u| u.user_id).collect(),
})
}
let groups: Vec<Group> = if let Some(user) = for_user {
let groups_without_users = match self.backend_handler.get_user_groups(&user).await {
Ok(groups) => groups,
Err(e) => {
return vec![lsr.gen_error(
LdapResultCode::Other,
format!(r#"Error while listing user groups: "{}": {}"#, lsr.base, e),
)]
}
};
match tokio_stream::iter(groups_without_users.iter())
.then(|g| async move { get_users_for_group::<Backend>(&self.backend_handler, g).await })
.try_collect::<Vec<Group>>()
.await
{
Ok(groups) => groups,
Err(e) => {
return vec![lsr.gen_error(
LdapResultCode::Other,
format!(r#"Error while listing user groups: "{}": {}"#, lsr.base, e),
)]
}
}
} else {
match self.backend_handler.list_groups().await {
Ok(groups) => groups,
Err(e) => {
return vec![lsr.gen_error(
LdapResultCode::Other,
format!(r#"Error while listing groups "{}": {}"#, lsr.base, e),
)]
}
}
};
groups
.into_iter()
.map(|u| make_ldap_search_group_result_entry(u, &self.base_dn_str, &lsr.attrs))
.map(|entry| Ok(lsr.gen_result_entry(entry?))) .map(|entry| Ok(lsr.gen_result_entry(entry?)))
// If the processing succeeds, add a success message at the end. // If the processing succeeds, add a success message at the end.
.chain(std::iter::once(Ok(lsr.gen_success()))) .chain(std::iter::once(Ok(lsr.gen_success())))
@ -261,7 +391,7 @@ impl<Backend: BackendHandler + LoginHandler> LdapHandler<Backend> {
} }
pub async fn handle_ldap_message(&mut self, server_op: ServerOps) -> Option<Vec<LdapMsg>> { pub async fn handle_ldap_message(&mut self, server_op: ServerOps) -> Option<Vec<LdapMsg>> {
let result = match server_op { Some(match server_op {
ServerOps::SimpleBind(sbr) => vec![self.do_bind(&sbr).await], ServerOps::SimpleBind(sbr) => vec![self.do_bind(&sbr).await],
ServerOps::Search(sr) => self.do_search(&sr).await, ServerOps::Search(sr) => self.do_search(&sr).await,
ServerOps::Unbind(_) => { ServerOps::Unbind(_) => {
@ -269,27 +399,46 @@ impl<Backend: BackendHandler + LoginHandler> LdapHandler<Backend> {
return None; return None;
} }
ServerOps::Whoami(wr) => vec![self.do_whoami(&wr)], ServerOps::Whoami(wr) => vec![self.do_whoami(&wr)],
}; })
Some(result)
} }
fn convert_filter(&self, filter: &LdapFilter) -> Result<RequestFilter> { fn get_group_filter(&self, filter: &LdapFilter) -> Result<Option<String>> {
match filter {
LdapFilter::Equality(field, value) => {
if field == "member" || field == "uniqueMember" {
let user_name = get_user_id_from_distinguished_name(
value,
&self.base_dn,
&self.base_dn_str,
)?;
Ok(Some(user_name))
} else if field == "objectClass" && value == "groupOfUniqueNames" {
Ok(None)
} else {
bail!("Unsupported group filter: {:?}", filter)
}
}
_ => bail!("Unsupported group filter: {:?}", filter),
}
}
fn convert_user_filter(&self, filter: &LdapFilter) -> Result<RequestFilter> {
match filter { match filter {
LdapFilter::And(filters) => Ok(RequestFilter::And( LdapFilter::And(filters) => Ok(RequestFilter::And(
filters filters
.iter() .iter()
.map(|f| self.convert_filter(f)) .map(|f| self.convert_user_filter(f))
.collect::<Result<_>>()?, .collect::<Result<_>>()?,
)), )),
LdapFilter::Or(filters) => Ok(RequestFilter::Or( LdapFilter::Or(filters) => Ok(RequestFilter::Or(
filters filters
.iter() .iter()
.map(|f| self.convert_filter(f)) .map(|f| self.convert_user_filter(f))
.collect::<Result<_>>()?, .collect::<Result<_>>()?,
)), )),
LdapFilter::Not(filter) => { LdapFilter::Not(filter) => Ok(RequestFilter::Not(Box::new(
Ok(RequestFilter::Not(Box::new(self.convert_filter(&*filter)?))) self.convert_user_filter(&*filter)?,
} ))),
LdapFilter::Equality(field, value) => { LdapFilter::Equality(field, value) => {
if field == "memberOf" { if field == "memberOf" {
let group_name = get_group_id_from_distinguished_name( let group_name = get_group_id_from_distinguished_name(
@ -298,19 +447,29 @@ impl<Backend: BackendHandler + LoginHandler> LdapHandler<Backend> {
&self.base_dn_str, &self.base_dn_str,
)?; )?;
Ok(RequestFilter::MemberOf(group_name)) Ok(RequestFilter::MemberOf(group_name))
} else if field == "objectClass" {
if value == "person"
|| value == "inetOrgPerson"
|| value == "posixAccount"
|| value == "mailAccount"
{
Ok(RequestFilter::And(vec![]))
} else {
Ok(RequestFilter::Not(Box::new(RequestFilter::And(vec![]))))
}
} else { } else {
Ok(RequestFilter::Equality(map_field(field)?, value.clone())) Ok(RequestFilter::Equality(map_field(field)?, value.clone()))
} }
} }
LdapFilter::Present(field) => { LdapFilter::Present(field) => {
// Check that it's a field we support. // Check that it's a field we support.
if field == "objectclass" || map_field(field).is_ok() { if field == "objectClass" || map_field(field).is_ok() {
Ok(RequestFilter::And(Vec::new())) Ok(RequestFilter::And(vec![]))
} else { } else {
Ok(RequestFilter::Not(Box::new(RequestFilter::And(Vec::new())))) Ok(RequestFilter::Not(Box::new(RequestFilter::And(vec![]))))
} }
} }
_ => bail!("Unsupported filter: {:?}", filter), _ => bail!("Unsupported user filter: {:?}", filter),
} }
} }
} }
@ -335,10 +494,10 @@ mod tests {
LdapHandler::new(mock, "dc=example,dc=com".to_string(), "test".to_string()); LdapHandler::new(mock, "dc=example,dc=com".to_string(), "test".to_string());
let request = SimpleBindRequest { let request = SimpleBindRequest {
msgid: 1, msgid: 1,
dn: "cn=test,dc=example,dc=com".to_string(), dn: "cn=test,ou=people,dc=example,dc=com".to_string(),
pw: "pass".to_string(), pw: "pass".to_string(),
}; };
ldap_handler.do_bind(&request).await; assert_eq!(ldap_handler.do_bind(&request).await, request.gen_success());
ldap_handler ldap_handler
} }
@ -396,7 +555,7 @@ mod tests {
let request = SimpleBindRequest { let request = SimpleBindRequest {
msgid: 2, msgid: 2,
dn: "cn=test,dc=example,dc=com".to_string(), dn: "cn=test,ou=people,dc=example,dc=com".to_string(),
pw: "pass".to_string(), pw: "pass".to_string(),
}; };
assert_eq!(ldap_handler.do_bind(&request).await, request.gen_success()); assert_eq!(ldap_handler.do_bind(&request).await, request.gen_success());
@ -404,7 +563,7 @@ mod tests {
let request = WhoamiRequest { msgid: 3 }; let request = WhoamiRequest { msgid: 3 };
assert_eq!( assert_eq!(
ldap_handler.do_whoami(&request), ldap_handler.do_whoami(&request),
request.gen_success("dn: cn=test,dc=example,dc=com") request.gen_success("dn: cn=test,ou=people,dc=example,dc=com")
); );
} }
@ -451,7 +610,7 @@ mod tests {
ldap_handler.do_search(&request).await, ldap_handler.do_search(&request).await,
vec![request.gen_error( vec![request.gen_error(
LdapResultCode::InsufficentAccessRights, LdapResultCode::InsufficentAccessRights,
r#"Current user is not allowed to query LDAP"#.to_string() r#"Current user `cn=test,ou=people,dc=example,dc=com` is not allowed to query LDAP, expected cn=admin,ou=people,dc=example,dc=com"#.to_string()
)] )]
); );
} }
@ -471,7 +630,7 @@ mod tests {
ldap_handler.do_bind(&request).await, ldap_handler.do_bind(&request).await,
request.gen_error( request.gen_error(
LdapResultCode::NamingViolation, LdapResultCode::NamingViolation,
r#"Wrong admin DN. Expected: "cn=admin,dc=example,dc=com""#.to_string() r#"Unexpected user DN format. Got "cn=bob,dc=example,dc=com", expected: "cn=username,ou=people,dc=example,dc=com""#.to_string()
) )
); );
let request = SimpleBindRequest { let request = SimpleBindRequest {
@ -483,7 +642,7 @@ mod tests {
ldap_handler.do_bind(&request).await, ldap_handler.do_bind(&request).await,
request.gen_error( request.gen_error(
LdapResultCode::NamingViolation, LdapResultCode::NamingViolation,
r#"Unexpected user DN format. Expected: "cn=username,ou=people,dc=example,dc=com""# r#"Unexpected user DN format. Got "cn=bob,ou=groups,dc=example,dc=com", expected: "cn=username,ou=people,dc=example,dc=com""#
.to_string() .to_string()
) )
); );
@ -559,14 +718,15 @@ mod tests {
ldap_handler.do_search(&request).await, ldap_handler.do_search(&request).await,
vec![ vec![
request.gen_result_entry(LdapSearchResultEntry { request.gen_result_entry(LdapSearchResultEntry {
dn: "cn=bob_1,dc=example,dc=com".to_string(), dn: "cn=bob_1,ou=people,dc=example,dc=com".to_string(),
attributes: vec![ attributes: vec![
LdapPartialAttribute { LdapPartialAttribute {
atype: "objectClass".to_string(), atype: "objectClass".to_string(),
vals: vec![ vals: vec![
"inetOrgPerson".to_string(), "inetOrgPerson".to_string(),
"posixAccount".to_string(), "posixAccount".to_string(),
"mailAccount".to_string() "mailAccount".to_string(),
"person".to_string()
] ]
}, },
LdapPartialAttribute { LdapPartialAttribute {
@ -592,14 +752,15 @@ mod tests {
], ],
}), }),
request.gen_result_entry(LdapSearchResultEntry { request.gen_result_entry(LdapSearchResultEntry {
dn: "cn=jim,dc=example,dc=com".to_string(), dn: "cn=jim,ou=people,dc=example,dc=com".to_string(),
attributes: vec![ attributes: vec![
LdapPartialAttribute { LdapPartialAttribute {
atype: "objectClass".to_string(), atype: "objectClass".to_string(),
vals: vec![ vals: vec![
"inetOrgPerson".to_string(), "inetOrgPerson".to_string(),
"posixAccount".to_string(), "posixAccount".to_string(),
"mailAccount".to_string() "mailAccount".to_string(),
"person".to_string()
] ]
}, },
LdapPartialAttribute { LdapPartialAttribute {
@ -674,7 +835,7 @@ mod tests {
ldap_handler.do_search(&request).await, ldap_handler.do_search(&request).await,
vec![request.gen_error( vec![request.gen_error(
LdapResultCode::UnwillingToPerform, LdapResultCode::UnwillingToPerform,
"Unsupported filter: Unsupported filter: Substring(\"uid\", LdapSubstringFilter { initial: None, any: [], final_: None })".to_string() "Unsupported user filter: Unsupported user filter: Substring(\"uid\", LdapSubstringFilter { initial: None, any: [], final_: None })".to_string()
)] )]
); );
} }

View File

@ -22,7 +22,10 @@ where
{ {
use futures_util::SinkExt; use futures_util::SinkExt;
use std::convert::TryFrom; use std::convert::TryFrom;
let server_op = match msg.map_err(|_e| ()).and_then(ServerOps::try_from) { let server_op = match msg
.map_err(|e| warn!("Error while receiving LDAP op: {:#}", e))
.and_then(ServerOps::try_from)
{
Ok(a_value) => a_value, Ok(a_value) => a_value,
Err(an_error) => { Err(an_error) => {
let _err = resp let _err = resp