Add support for basic ldap filters

This commit is contained in:
Valentin Tolmer 2021-04-07 20:55:23 +02:00
parent 6abe94af13
commit bfd7730d55
2 changed files with 132 additions and 35 deletions

View File

@ -10,9 +10,17 @@ pub struct BindRequest {
pub password: String, pub password: String,
} }
#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
pub enum RequestFilter {
And(Vec<RequestFilter>),
Or(Vec<RequestFilter>),
Not(Box<RequestFilter>),
Equality(String, String),
}
#[cfg_attr(test, derive(PartialEq, Eq, Debug))] #[cfg_attr(test, derive(PartialEq, Eq, Debug))]
pub struct ListUsersRequest { pub struct ListUsersRequest {
// filters pub filters: Option<RequestFilter>,
} }
#[cfg_attr(test, derive(PartialEq, Eq, Debug))] #[cfg_attr(test, derive(PartialEq, Eq, Debug))]

View File

@ -1,4 +1,4 @@
use crate::domain::handler::{BackendHandler, ListUsersRequest, User}; use crate::domain::handler::{BackendHandler, ListUsersRequest, RequestFilter, User};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use ldap3_server::simple::*; use ldap3_server::simple::*;
@ -77,6 +77,28 @@ fn is_subtree(subtree: &[(String, String)], base_tree: &[(String, String)]) -> b
true true
} }
fn convert_filter(filter: &LdapFilter) -> Result<RequestFilter> {
match filter {
LdapFilter::And(filters) => Ok(RequestFilter::And(
filters
.into_iter()
.map(convert_filter)
.collect::<Result<_>>()?,
)),
LdapFilter::Or(filters) => Ok(RequestFilter::Or(
filters
.into_iter()
.map(convert_filter)
.collect::<Result<_>>()?,
)),
LdapFilter::Not(filter) => Ok(RequestFilter::Not(Box::new(convert_filter(&*filter)?))),
LdapFilter::Equality(field, value) => {
Ok(RequestFilter::Equality(field.clone(), value.clone()))
}
_ => bail!("Unsupported filter"),
}
}
pub struct LdapHandler<Backend: BackendHandler> { pub struct LdapHandler<Backend: BackendHandler> {
dn: String, dn: String,
backend_handler: Backend, backend_handler: Backend,
@ -138,7 +160,20 @@ impl<Backend: BackendHandler> LdapHandler<Backend> {
// 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 users = match self.backend_handler.list_users(ListUsersRequest {}).await { let filters = match convert_filter(&lsr.filter) {
Ok(f) => Some(f),
Err(_) => {
return vec![lsr.gen_error(
LdapResultCode::UnwillingToPerform,
"Unsupported filter".to_string(),
)]
}
};
let users = match self
.backend_handler
.list_users(ListUsersRequest { filters })
.await
{
Ok(users) => users, Ok(users) => users,
Err(e) => { Err(e) => {
return vec![lsr.gen_error( return vec![lsr.gen_error(
@ -188,6 +223,21 @@ mod tests {
use mockall::predicate::eq; use mockall::predicate::eq;
use tokio; use tokio;
async fn setup_bound_handler(
mut mock: MockTestBackendHandler,
) -> LdapHandler<MockTestBackendHandler> {
mock.expect_bind().return_once(|_| Ok(()));
let mut ldap_handler =
LdapHandler::new(mock, "dc=example,dc=com".to_string(), "test".to_string());
let request = SimpleBindRequest {
msgid: 1,
dn: "test".to_string(),
pw: "pass".to_string(),
};
ldap_handler.do_bind(&request).await;
ldap_handler
}
#[tokio::test] #[tokio::test]
async fn test_bind() { async fn test_bind() {
let mut mock = MockTestBackendHandler::new(); let mut mock = MockTestBackendHandler::new();
@ -300,38 +350,27 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_search() { async fn test_search() {
let mut mock = MockTestBackendHandler::new(); let mut mock = MockTestBackendHandler::new();
mock.expect_bind().return_once(|_| Ok(())); mock.expect_list_users().times(1).return_once(|_| {
mock.expect_list_users() Ok(vec![
.with(eq(ListUsersRequest {})) User {
.times(1) user_id: "bob_1".to_string(),
.return_once(|_| { email: "bob@bobmail.bob".to_string(),
Ok(vec![ display_name: "Bôb Böbberson".to_string(),
User { first_name: "Bôb".to_string(),
user_id: "bob_1".to_string(), last_name: "Böbberson".to_string(),
email: "bob@bobmail.bob".to_string(), creation_date: NaiveDateTime::from_timestamp(1_000_000_000, 0),
display_name: "Bôb Böbberson".to_string(), },
first_name: "Bôb".to_string(), User {
last_name: "Böbberson".to_string(), user_id: "jim".to_string(),
creation_date: NaiveDateTime::from_timestamp(1_000_000_000, 0), email: "jim@cricket.jim".to_string(),
}, display_name: "Jimminy Cricket".to_string(),
User { first_name: "Jim".to_string(),
user_id: "jim".to_string(), last_name: "Cricket".to_string(),
email: "jim@cricket.jim".to_string(), creation_date: NaiveDateTime::from_timestamp(1_003_000_000, 0),
display_name: "Jimminy Cricket".to_string(), },
first_name: "Jim".to_string(), ])
last_name: "Cricket".to_string(), });
creation_date: NaiveDateTime::from_timestamp(1_003_000_000, 0), let mut ldap_handler = setup_bound_handler(mock).await;
},
])
});
let mut ldap_handler =
LdapHandler::new(mock, "dc=example,dc=com".to_string(), "test".to_string());
let request = SimpleBindRequest {
msgid: 1,
dn: "test".to_string(),
pw: "pass".to_string(),
};
assert_eq!(ldap_handler.do_bind(&request).await, request.gen_success());
let request = SearchRequest { let request = SearchRequest {
msgid: 2, msgid: 2,
base: "ou=people,dc=example,dc=com".to_string(), base: "ou=people,dc=example,dc=com".to_string(),
@ -419,4 +458,54 @@ mod tests {
] ]
); );
} }
#[tokio::test]
async fn test_search_filters() {
let mut mock = MockTestBackendHandler::new();
mock.expect_list_users()
.with(eq(ListUsersRequest {
filters: Some(RequestFilter::And(vec![RequestFilter::Or(vec![
RequestFilter::Not(Box::new(RequestFilter::Equality(
"uid".to_string(),
"bob".to_string(),
))),
])])),
}))
.times(1)
.return_once(|_| Ok(vec![]));
let mut ldap_handler = setup_bound_handler(mock).await;
let request = SearchRequest {
msgid: 2,
base: "ou=people,dc=example,dc=com".to_string(),
scope: LdapSearchScope::Base,
filter: LdapFilter::And(vec![LdapFilter::Or(vec![LdapFilter::Not(Box::new(
LdapFilter::Equality("uid".to_string(), "bob".to_string()),
))])]),
attrs: vec!["objectClass".to_string()],
};
assert_eq!(
ldap_handler.do_search(&request).await,
vec![request.gen_success()]
);
}
#[tokio::test]
async fn test_search_unsupported_filters() {
let mut mock = MockTestBackendHandler::new();
let mut ldap_handler = setup_bound_handler(mock).await;
let request = SearchRequest {
msgid: 2,
base: "ou=people,dc=example,dc=com".to_string(),
scope: LdapSearchScope::Base,
filter: LdapFilter::Present("uid".to_string()),
attrs: vec!["objectClass".to_string()],
};
assert_eq!(
ldap_handler.do_search(&request).await,
vec![request.gen_error(
LdapResultCode::UnwillingToPerform,
"Unsupported filter".to_string()
)]
);
}
} }