ldap: add support for memberOf attribute

The "memberOf" filter was already supported, but not the attribute.

Fixes #179
This commit is contained in:
Valentin Tolmer 2022-06-10 15:08:13 +02:00 committed by nitnelave
parent 1f632a8069
commit da186fab38
5 changed files with 342 additions and 159 deletions

View File

@ -34,7 +34,7 @@ impl From<String> for UserId {
} }
} }
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)] #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(not(target_arch = "wasm32"), derive(sqlx::FromRow))] #[cfg_attr(not(target_arch = "wasm32"), derive(sqlx::FromRow))]
pub struct User { pub struct User {
pub user_id: UserId, pub user_id: UserId,
@ -134,9 +134,19 @@ pub struct GroupId(pub i32);
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::FromRow)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::FromRow)]
pub struct GroupIdAndName(pub GroupId, pub String); pub struct GroupIdAndName(pub GroupId, pub String);
#[derive(Debug, Clone, PartialEq)]
pub struct UserAndGroups {
pub user: User,
pub groups: Option<Vec<GroupIdAndName>>,
}
#[async_trait] #[async_trait]
pub trait BackendHandler: Clone + Send { pub trait BackendHandler: Clone + Send {
async fn list_users(&self, filters: Option<UserRequestFilter>) -> Result<Vec<User>>; async fn list_users(
&self,
filters: Option<UserRequestFilter>,
get_groups: bool,
) -> Result<Vec<UserAndGroups>>;
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>; async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
async fn get_user_details(&self, user_id: &UserId) -> Result<User>; async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>; async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>;
@ -159,7 +169,7 @@ mockall::mock! {
} }
#[async_trait] #[async_trait]
impl BackendHandler for TestBackendHandler { impl BackendHandler for TestBackendHandler {
async fn list_users(&self, filters: Option<UserRequestFilter>) -> Result<Vec<User>>; async fn list_users(&self, filters: Option<UserRequestFilter>, get_groups: bool) -> Result<Vec<UserAndGroups>>;
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>; async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
async fn get_user_details(&self, user_id: &UserId) -> Result<User>; async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>; async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>;

View File

@ -3,7 +3,7 @@ use crate::infra::configuration::Configuration;
use async_trait::async_trait; use async_trait::async_trait;
use futures_util::StreamExt; use futures_util::StreamExt;
use sea_query::{Expr, Iden, Order, Query, SimpleExpr}; use sea_query::{Expr, Iden, Order, Query, SimpleExpr};
use sqlx::Row; use sqlx::{FromRow, Row};
use std::collections::HashSet; use std::collections::HashSet;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -109,7 +109,11 @@ fn get_group_filter_expr(filter: GroupRequestFilter) -> SimpleExpr {
#[async_trait] #[async_trait]
impl BackendHandler for SqlBackendHandler { impl BackendHandler for SqlBackendHandler {
async fn list_users(&self, filters: Option<UserRequestFilter>) -> Result<Vec<User>> { async fn list_users(
&self,
filters: Option<UserRequestFilter>,
get_groups: bool,
) -> Result<Vec<UserAndGroups>> {
let query = { let query = {
let mut query_builder = Query::select() let mut query_builder = Query::select()
.column((Users::Table, Users::UserId)) .column((Users::Table, Users::UserId))
@ -122,6 +126,26 @@ impl BackendHandler for SqlBackendHandler {
.from(Users::Table) .from(Users::Table)
.order_by((Users::Table, Users::UserId), Order::Asc) .order_by((Users::Table, Users::UserId), Order::Asc)
.to_owned(); .to_owned();
let add_join_group_tables = |builder: &mut sea_query::SelectStatement| {
builder
.left_join(
Memberships::Table,
Expr::tbl(Users::Table, Users::UserId)
.equals(Memberships::Table, Memberships::UserId),
)
.left_join(
Groups::Table,
Expr::tbl(Memberships::Table, Memberships::GroupId)
.equals(Groups::Table, Groups::GroupId),
);
};
if get_groups {
add_join_group_tables(&mut query_builder);
query_builder
.column((Groups::Table, Groups::GroupId))
.column((Groups::Table, Groups::DisplayName))
.order_by((Groups::Table, Groups::DisplayName), Order::Asc);
}
if let Some(filter) = filters { if let Some(filter) = filters {
if filter == UserRequestFilter::Not(Box::new(UserRequestFilter::And(Vec::new()))) { if filter == UserRequestFilter::Not(Box::new(UserRequestFilter::And(Vec::new()))) {
return Ok(Vec::new()); return Ok(Vec::new());
@ -131,31 +155,48 @@ impl BackendHandler for SqlBackendHandler {
{ {
let (RequiresGroup(requires_group), condition) = get_user_filter_expr(filter); let (RequiresGroup(requires_group), condition) = get_user_filter_expr(filter);
query_builder.and_where(condition); query_builder.and_where(condition);
if requires_group { if requires_group && !get_groups {
query_builder add_join_group_tables(&mut query_builder);
.left_join(
Memberships::Table,
Expr::tbl(Users::Table, Users::UserId)
.equals(Memberships::Table, Memberships::UserId),
)
.left_join(
Groups::Table,
Expr::tbl(Memberships::Table, Memberships::GroupId)
.equals(Groups::Table, Groups::GroupId),
);
} }
} }
} }
query_builder.to_string(DbQueryBuilder {}) query_builder.to_string(DbQueryBuilder {})
}; };
log::error!("query: {}", &query);
let results = sqlx::query_as::<_, User>(&query) // For group_by.
.fetch(&self.sql_pool) use itertools::Itertools;
.collect::<Vec<sqlx::Result<User>>>() let mut users = Vec::new();
.await; // The rows are returned sorted by user_id. We group them by
// this key which gives us one element (`rows`) per group.
for (_, rows) in &sqlx::query(&query)
.fetch_all(&self.sql_pool)
.await?
.into_iter()
.group_by(|row| row.get::<UserId, _>(&*Users::UserId.to_string()))
{
let mut rows = rows.peekable();
users.push(UserAndGroups {
user: User::from_row(rows.peek().unwrap()).unwrap(),
groups: if get_groups {
Some(
rows.map(|row| {
GroupIdAndName(
GroupId(row.get::<i32, _>(&*Groups::GroupId.to_string())),
row.get::<String, _>(&*Groups::DisplayName.to_string()),
)
})
.filter(|g| !g.1.is_empty())
.collect(),
)
} else {
None
},
});
}
Ok(results.into_iter().collect::<sqlx::Result<Vec<User>>>()?) Ok(users)
} }
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>> { async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>> {
@ -486,6 +527,19 @@ mod tests {
.unwrap(); .unwrap();
} }
async fn get_user_names(
handler: &SqlBackendHandler,
filters: Option<UserRequestFilter>,
) -> Vec<String> {
handler
.list_users(filters, false)
.await
.unwrap()
.into_iter()
.map(|u| u.user.user_id.to_string())
.collect::<Vec<_>>()
}
#[tokio::test] #[tokio::test]
async fn test_bind_admin() { async fn test_bind_admin() {
let sql_pool = get_in_memory_db().await; let sql_pool = get_in_memory_db().await;
@ -558,50 +612,70 @@ mod tests {
insert_user(&handler, "bob", "bob00").await; insert_user(&handler, "bob", "bob00").await;
insert_user(&handler, "patrick", "pass").await; insert_user(&handler, "patrick", "pass").await;
insert_user(&handler, "John", "Pa33w0rd!").await; insert_user(&handler, "John", "Pa33w0rd!").await;
let group_1 = insert_group(&handler, "Best Group").await;
let group_2 = insert_group(&handler, "Worst Group").await;
insert_membership(&handler, group_1, "bob").await;
insert_membership(&handler, group_1, "patrick").await;
insert_membership(&handler, group_2, "patrick").await;
insert_membership(&handler, group_2, "John").await;
{ {
let users = handler let users = get_user_names(&handler, None).await;
.list_users(None)
.await
.unwrap()
.into_iter()
.map(|u| u.user_id.to_string())
.collect::<Vec<_>>();
assert_eq!(users, vec!["bob", "john", "patrick"]); assert_eq!(users, vec!["bob", "john", "patrick"]);
} }
{ {
let users = handler let users = get_user_names(
.list_users(Some(UserRequestFilter::UserId(UserId::new("bob")))) &handler,
.await Some(UserRequestFilter::UserId(UserId::new("bob"))),
.unwrap() )
.into_iter() .await;
.map(|u| u.user_id.to_string())
.collect::<Vec<_>>();
assert_eq!(users, vec!["bob"]); assert_eq!(users, vec!["bob"]);
} }
{ {
let users = handler let users = get_user_names(
.list_users(Some(UserRequestFilter::Or(vec![ &handler,
Some(UserRequestFilter::Or(vec![
UserRequestFilter::UserId(UserId::new("bob")), UserRequestFilter::UserId(UserId::new("bob")),
UserRequestFilter::UserId(UserId::new("John")), UserRequestFilter::UserId(UserId::new("John")),
]))) ])),
.await )
.unwrap() .await;
.into_iter()
.map(|u| u.user_id.to_string())
.collect::<Vec<_>>();
assert_eq!(users, vec!["bob", "john"]); assert_eq!(users, vec!["bob", "john"]);
} }
{
let users = get_user_names(
&handler,
Some(UserRequestFilter::Not(Box::new(UserRequestFilter::UserId(
UserId::new("bob"),
)))),
)
.await;
assert_eq!(users, vec!["john", "patrick"]);
}
{ {
let users = handler let users = handler
.list_users(Some(UserRequestFilter::Not(Box::new( .list_users(None, true)
UserRequestFilter::UserId(UserId::new("bob")),
))))
.await .await
.unwrap() .unwrap()
.into_iter() .into_iter()
.map(|u| u.user_id.to_string()) .map(|u| {
(
u.user.user_id.to_string(),
u.groups
.unwrap()
.into_iter()
.map(|g| g.0)
.collect::<Vec<_>>(),
)
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!(users, vec!["john", "patrick"]); assert_eq!(
users,
vec![
("bob".to_string(), vec![group_1]),
("john".to_string(), vec![group_2]),
("patrick".to_string(), vec![group_1, group_2]),
]
);
} }
} }
@ -763,29 +837,13 @@ mod tests {
// Remove a user // Remove a user
let _request_result = handler.delete_user(&UserId::new("Jennz")).await.unwrap(); let _request_result = handler.delete_user(&UserId::new("Jennz")).await.unwrap();
let users = handler assert_eq!(get_user_names(&handler, None).await, vec!["hector", "val"]);
.list_users(None)
.await
.unwrap()
.into_iter()
.map(|u| u.user_id.to_string())
.collect::<Vec<_>>();
assert_eq!(users, vec!["hector", "val"]);
// Insert new user and remove two // Insert new user and remove two
insert_user(&handler, "NewBoi", "Joni").await; insert_user(&handler, "NewBoi", "Joni").await;
let _request_result = handler.delete_user(&UserId::new("Hector")).await.unwrap(); let _request_result = handler.delete_user(&UserId::new("Hector")).await.unwrap();
let _request_result = handler.delete_user(&UserId::new("NewBoi")).await.unwrap(); let _request_result = handler.delete_user(&UserId::new("NewBoi")).await.unwrap();
let users = handler assert_eq!(get_user_names(&handler, None).await, vec!["val"]);
.list_users(None)
.await
.unwrap()
.into_iter()
.map(|u| u.user_id.to_string())
.collect::<Vec<_>>();
assert_eq!(users, vec!["val"]);
} }
} }

View File

@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
type DomainRequestFilter = crate::domain::handler::UserRequestFilter; type DomainRequestFilter = crate::domain::handler::UserRequestFilter;
type DomainUser = crate::domain::handler::User; type DomainUser = crate::domain::handler::User;
type DomainGroup = crate::domain::handler::Group; type DomainGroup = crate::domain::handler::Group;
type DomainUserAndGroups = crate::domain::handler::UserAndGroups;
use super::api::Context; use super::api::Context;
#[derive(PartialEq, Eq, Debug, GraphQLInputObject)] #[derive(PartialEq, Eq, Debug, GraphQLInputObject)]
@ -126,7 +127,7 @@ impl<Handler: BackendHandler + Sync> Query<Handler> {
} }
Ok(context Ok(context
.handler .handler
.list_users(filters.map(TryInto::try_into).transpose()?) .list_users(filters.map(TryInto::try_into).transpose()?, false)
.await .await
.map(|v| v.into_iter().map(Into::into).collect())?) .map(|v| v.into_iter().map(Into::into).collect())?)
} }
@ -215,6 +216,15 @@ impl<Handler: BackendHandler> From<DomainUser> for User<Handler> {
} }
} }
impl<Handler: BackendHandler> From<DomainUserAndGroups> for User<Handler> {
fn from(user: DomainUserAndGroups) -> Self {
Self {
user: user.user,
_phantom: std::marker::PhantomData,
}
}
}
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)] #[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
/// Represents a single group. /// Represents a single group.
pub struct Group<Handler: BackendHandler> { pub struct Group<Handler: BackendHandler> {
@ -239,9 +249,10 @@ impl<Handler: BackendHandler + Sync> Group<Handler> {
} }
Ok(context Ok(context
.handler .handler
.list_users(Some(DomainRequestFilter::MemberOfId(GroupId( .list_users(
self.group_id, Some(DomainRequestFilter::MemberOfId(GroupId(self.group_id))),
)))) false,
)
.await .await
.map(|v| v.into_iter().map(Into::into).collect())?) .map(|v| v.into_iter().map(Into::into).collect())?)
} }
@ -365,21 +376,33 @@ mod tests {
let mut mock = MockTestBackendHandler::new(); let mut mock = MockTestBackendHandler::new();
mock.expect_list_users() mock.expect_list_users()
.with(eq(Some(UserRequestFilter::Or(vec![ .with(
UserRequestFilter::Equality("id".to_string(), "bob".to_string()), eq(Some(UserRequestFilter::Or(vec![
UserRequestFilter::Equality("email".to_string(), "robert@bobbers.on".to_string()), UserRequestFilter::Equality("id".to_string(), "bob".to_string()),
])))) UserRequestFilter::Equality(
.return_once(|_| { "email".to_string(),
"robert@bobbers.on".to_string(),
),
]))),
eq(false),
)
.return_once(|_, _| {
Ok(vec![ Ok(vec![
DomainUser { DomainUserAndGroups {
user_id: UserId::new("bob"), user: DomainUser {
email: "bob@bobbers.on".to_string(), user_id: UserId::new("bob"),
..Default::default() email: "bob@bobbers.on".to_string(),
..Default::default()
},
groups: None,
}, },
DomainUser { DomainUserAndGroups {
user_id: UserId::new("robert"), user: DomainUser {
email: "robert@bobbers.on".to_string(), user_id: UserId::new("robert"),
..Default::default() email: "robert@bobbers.on".to_string(),
..Default::default()
},
groups: None,
}, },
]) ])
}); });

View File

@ -1,8 +1,8 @@
use crate::{ use crate::{
domain::{ domain::{
handler::{ handler::{
BackendHandler, BindRequest, Group, GroupRequestFilter, LoginHandler, User, UserId, BackendHandler, BindRequest, Group, GroupIdAndName, GroupRequestFilter, LoginHandler,
UserRequestFilter, User, UserId, UserRequestFilter,
}, },
opaque_handler::OpaqueHandler, opaque_handler::OpaqueHandler,
}, },
@ -109,6 +109,8 @@ fn get_user_attribute(
user: &User, user: &User,
attribute: &str, attribute: &str,
dn: &str, dn: &str,
base_dn_str: &str,
groups: Option<&[GroupIdAndName]>,
ignored_user_attributes: &[String], ignored_user_attributes: &[String],
) -> Result<Option<Vec<String>>> { ) -> Result<Option<Vec<String>>> {
let attribute = attribute.to_ascii_lowercase(); let attribute = attribute.to_ascii_lowercase();
@ -124,6 +126,11 @@ fn get_user_attribute(
"mail" => vec![user.email.clone()], "mail" => vec![user.email.clone()],
"givenname" => vec![user.first_name.clone()], "givenname" => vec![user.first_name.clone()],
"sn" => vec![user.last_name.clone()], "sn" => vec![user.last_name.clone()],
"memberof" => groups
.into_iter()
.flatten()
.map(|id_and_name| format!("uid={},ou=groups,{}", &id_and_name.1, base_dn_str))
.collect(),
"cn" | "displayname" => vec![user.display_name.clone()], "cn" | "displayname" => vec![user.display_name.clone()],
"createtimestamp" | "modifytimestamp" => vec![user.creation_date.to_rfc3339()], "createtimestamp" | "modifytimestamp" => vec![user.creation_date.to_rfc3339()],
"1.1" => return Ok(None), "1.1" => return Ok(None),
@ -179,17 +186,24 @@ fn make_ldap_search_user_result_entry(
user: User, user: User,
base_dn_str: &str, base_dn_str: &str,
attributes: &[String], attributes: &[String],
groups: Option<&[GroupIdAndName]>,
ignored_user_attributes: &[String], ignored_user_attributes: &[String],
) -> Result<LdapSearchResultEntry> { ) -> Result<LdapSearchResultEntry> {
let dn = format!("uid={},ou=people,{}", user.user_id.as_str(), base_dn_str); let dn = format!("uid={},ou=people,{}", user.user_id.as_str(), base_dn_str);
let expanded_attributes = expand_attribute_wildcards(attributes, ALL_USER_ATTRIBUTE_KEYS);
Ok(LdapSearchResultEntry { Ok(LdapSearchResultEntry {
dn: dn.clone(), dn: dn.clone(),
attributes: expanded_attributes attributes: attributes
.iter() .iter()
.filter_map(|a| { .filter_map(|a| {
let values = match get_user_attribute(&user, a, &dn, ignored_user_attributes) { let values = match get_user_attribute(
&user,
a,
&dn,
base_dn_str,
groups,
ignored_user_attributes,
) {
Err(e) => return Some(Err(e)), Err(e) => return Some(Err(e)),
Ok(v) => v, Ok(v) => v,
}?; }?;
@ -625,7 +639,16 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
UserRequestFilter::And(vec![filters, UserRequestFilter::UserId((*u).clone())]) UserRequestFilter::And(vec![filters, UserRequestFilter::UserId((*u).clone())])
} }
}; };
let users = match self.backend_handler.list_users(Some(filters)).await { let expanded_attributes =
expand_attribute_wildcards(&request.attrs, ALL_USER_ATTRIBUTE_KEYS);
let need_groups = expanded_attributes
.iter()
.any(|s| s.to_ascii_lowercase() == "memberof");
let users = match self
.backend_handler
.list_users(Some(filters), need_groups)
.await
{
Ok(users) => users, Ok(users) => users,
Err(e) => { Err(e) => {
return vec![make_search_error( return vec![make_search_error(
@ -639,9 +662,10 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
.into_iter() .into_iter()
.map(|u| { .map(|u| {
make_ldap_search_user_result_entry( make_ldap_search_user_result_entry(
u, u.user,
&self.base_dn_str, &self.base_dn_str,
&request.attrs, &expanded_attributes,
u.groups.as_deref(),
&self.ignored_user_attributes, &self.ignored_user_attributes,
) )
}) })
@ -903,7 +927,7 @@ mod tests {
} }
#[async_trait] #[async_trait]
impl BackendHandler for TestBackendHandler { impl BackendHandler for TestBackendHandler {
async fn list_users(&self, filters: Option<UserRequestFilter>) -> Result<Vec<User>>; async fn list_users(&self, filters: Option<UserRequestFilter>, get_groups: bool) -> Result<Vec<UserAndGroups>>;
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>; async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
async fn get_user_details(&self, user_id: &UserId) -> Result<User>; async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>; async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>;
@ -1070,15 +1094,21 @@ mod tests {
async fn test_search_regular_user() { async fn test_search_regular_user() {
let mut mock = MockTestBackendHandler::new(); let mut mock = MockTestBackendHandler::new();
mock.expect_list_users() mock.expect_list_users()
.with(eq(Some(UserRequestFilter::And(vec![ .with(
UserRequestFilter::And(vec![]), eq(Some(UserRequestFilter::And(vec![
UserRequestFilter::UserId(UserId::new("test")), UserRequestFilter::And(vec![]),
])))) UserRequestFilter::UserId(UserId::new("test")),
]))),
eq(false),
)
.times(1) .times(1)
.return_once(|_| { .return_once(|_, _| {
Ok(vec![User { Ok(vec![UserAndGroups {
user_id: UserId::new("test"), user: User {
..Default::default() user_id: UserId::new("test"),
..Default::default()
},
groups: None,
}]) }])
}); });
let mut ldap_handler = setup_bound_handler_with_group(mock, "regular").await; let mut ldap_handler = setup_bound_handler_with_group(mock, "regular").await;
@ -1101,9 +1131,9 @@ mod tests {
async fn test_search_readonly_user() { async fn test_search_readonly_user() {
let mut mock = MockTestBackendHandler::new(); let mut mock = MockTestBackendHandler::new();
mock.expect_list_users() mock.expect_list_users()
.with(eq(Some(UserRequestFilter::And(vec![])))) .with(eq(Some(UserRequestFilter::And(vec![]))), eq(false))
.times(1) .times(1)
.return_once(|_| Ok(vec![])); .return_once(|_, _| Ok(vec![]));
let mut ldap_handler = setup_bound_readonly_handler(mock).await; let mut ldap_handler = setup_bound_readonly_handler(mock).await;
let request = let request =
@ -1114,6 +1144,42 @@ mod tests {
); );
} }
#[tokio::test]
async fn test_search_member_of() {
let mut mock = MockTestBackendHandler::new();
mock.expect_list_users()
.with(eq(Some(UserRequestFilter::And(vec![]))), eq(true))
.times(1)
.return_once(|_, _| {
Ok(vec![UserAndGroups {
user: User {
user_id: UserId::new("bob"),
..Default::default()
},
groups: Some(vec![GroupIdAndName(GroupId(42), "rockstars".to_string())]),
}])
});
let mut ldap_handler = setup_bound_readonly_handler(mock).await;
let request = make_user_search_request::<String>(
LdapFilter::And(vec![]),
vec!["memberOf".to_string()],
);
assert_eq!(
ldap_handler.do_search(&request).await,
vec![
LdapOp::SearchResultEntry(LdapSearchResultEntry {
dn: "uid=bob,ou=people,dc=example,dc=com".to_string(),
attributes: vec![LdapPartialAttribute {
atype: "memberOf".to_string(),
vals: vec!["uid=rockstars,ou=groups,dc=example,dc=com".to_string()]
}],
}),
make_search_success(),
],
);
}
#[tokio::test] #[tokio::test]
async fn test_bind_invalid_dn() { async fn test_bind_invalid_dn() {
let mock = MockTestBackendHandler::new(); let mock = MockTestBackendHandler::new();
@ -1199,23 +1265,29 @@ mod tests {
async fn test_search_users() { async fn test_search_users() {
use chrono::prelude::*; use chrono::prelude::*;
let mut mock = MockTestBackendHandler::new(); let mut mock = MockTestBackendHandler::new();
mock.expect_list_users().times(1).return_once(|_| { mock.expect_list_users().times(1).return_once(|_, _| {
Ok(vec![ Ok(vec![
User { UserAndGroups {
user_id: UserId::new("bob_1"), user: User {
email: "bob@bobmail.bob".to_string(), user_id: UserId::new("bob_1"),
display_name: "Bôb Böbberson".to_string(), email: "bob@bobmail.bob".to_string(),
first_name: "Bôb".to_string(), display_name: "Bôb Böbberson".to_string(),
last_name: "Böbberson".to_string(), first_name: "Bôb".to_string(),
..Default::default() last_name: "Böbberson".to_string(),
..Default::default()
},
groups: None,
}, },
User { UserAndGroups {
user_id: UserId::new("jim"), user: User {
email: "jim@cricket.jim".to_string(), user_id: UserId::new("jim"),
display_name: "Jimminy Cricket".to_string(), email: "jim@cricket.jim".to_string(),
first_name: "Jim".to_string(), display_name: "Jimminy Cricket".to_string(),
last_name: "Cricket".to_string(), first_name: "Jim".to_string(),
creation_date: Utc.ymd(2014, 7, 8).and_hms(9, 10, 11), last_name: "Cricket".to_string(),
creation_date: Utc.ymd(2014, 7, 8).and_hms(9, 10, 11),
},
groups: None,
}, },
]) ])
}); });
@ -1559,19 +1631,24 @@ mod tests {
async fn test_search_filters() { async fn test_search_filters() {
let mut mock = MockTestBackendHandler::new(); let mut mock = MockTestBackendHandler::new();
mock.expect_list_users() mock.expect_list_users()
.with(eq(Some(UserRequestFilter::And(vec![ .with(
UserRequestFilter::Or(vec![ eq(Some(UserRequestFilter::And(vec![UserRequestFilter::Or(
UserRequestFilter::Not(Box::new(UserRequestFilter::UserId(UserId::new("bob")))), vec![
UserRequestFilter::And(vec![]), UserRequestFilter::Not(Box::new(UserRequestFilter::UserId(UserId::new(
UserRequestFilter::Not(Box::new(UserRequestFilter::And(vec![]))), "bob",
UserRequestFilter::And(vec![]), )))),
UserRequestFilter::And(vec![]), UserRequestFilter::And(vec![]),
UserRequestFilter::Not(Box::new(UserRequestFilter::And(vec![]))), UserRequestFilter::Not(Box::new(UserRequestFilter::And(vec![]))),
UserRequestFilter::Not(Box::new(UserRequestFilter::And(vec![]))), UserRequestFilter::And(vec![]),
]), UserRequestFilter::And(vec![]),
])))) UserRequestFilter::Not(Box::new(UserRequestFilter::And(vec![]))),
UserRequestFilter::Not(Box::new(UserRequestFilter::And(vec![]))),
],
)]))),
eq(false),
)
.times(1) .times(1)
.return_once(|_| Ok(vec![])); .return_once(|_, _| Ok(vec![]));
let mut ldap_handler = setup_bound_admin_handler(mock).await; let mut ldap_handler = setup_bound_admin_handler(mock).await;
let request = make_user_search_request( let request = make_user_search_request(
LdapFilter::And(vec![LdapFilter::Or(vec![ LdapFilter::And(vec![LdapFilter::Or(vec![
@ -1595,12 +1672,15 @@ mod tests {
} }
#[tokio::test] #[tokio::test]
async fn test_search_member_of() { async fn test_search_member_of_filter() {
let mut mock = MockTestBackendHandler::new(); let mut mock = MockTestBackendHandler::new();
mock.expect_list_users() mock.expect_list_users()
.with(eq(Some(UserRequestFilter::MemberOf("group_1".to_string())))) .with(
eq(Some(UserRequestFilter::MemberOf("group_1".to_string()))),
eq(false),
)
.times(1) .times(1)
.return_once(|_| Ok(vec![])); .return_once(|_, _| Ok(vec![]));
let mut ldap_handler = setup_bound_admin_handler(mock).await; let mut ldap_handler = setup_bound_admin_handler(mock).await;
let request = make_user_search_request( let request = make_user_search_request(
LdapFilter::Equality( LdapFilter::Equality(
@ -1644,16 +1724,22 @@ mod tests {
async fn test_search_filters_lowercase() { async fn test_search_filters_lowercase() {
let mut mock = MockTestBackendHandler::new(); let mut mock = MockTestBackendHandler::new();
mock.expect_list_users() mock.expect_list_users()
.with(eq(Some(UserRequestFilter::And(vec![ .with(
UserRequestFilter::Or(vec![UserRequestFilter::Not(Box::new( eq(Some(UserRequestFilter::And(vec![UserRequestFilter::Or(
UserRequestFilter::Equality("first_name".to_string(), "bob".to_string()), vec![UserRequestFilter::Not(Box::new(
))]), UserRequestFilter::Equality("first_name".to_string(), "bob".to_string()),
])))) ))],
)]))),
eq(false),
)
.times(1) .times(1)
.return_once(|_| { .return_once(|_, _| {
Ok(vec![User { Ok(vec![UserAndGroups {
user_id: UserId::new("bob_1"), user: User {
..Default::default() user_id: UserId::new("bob_1"),
..Default::default()
},
groups: None,
}]) }])
}); });
let mut ldap_handler = setup_bound_admin_handler(mock).await; let mut ldap_handler = setup_bound_admin_handler(mock).await;
@ -1686,14 +1772,17 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_search_both() { async fn test_search_both() {
let mut mock = MockTestBackendHandler::new(); let mut mock = MockTestBackendHandler::new();
mock.expect_list_users().times(1).return_once(|_| { mock.expect_list_users().times(1).return_once(|_, _| {
Ok(vec![User { Ok(vec![UserAndGroups {
user_id: UserId::new("bob_1"), user: User {
email: "bob@bobmail.bob".to_string(), user_id: UserId::new("bob_1"),
display_name: "Bôb Böbberson".to_string(), email: "bob@bobmail.bob".to_string(),
first_name: "Bôb".to_string(), display_name: "Bôb Böbberson".to_string(),
last_name: "Böbberson".to_string(), first_name: "Bôb".to_string(),
..Default::default() last_name: "Böbberson".to_string(),
..Default::default()
},
groups: None,
}]) }])
}); });
mock.expect_list_groups() mock.expect_list_groups()
@ -1763,14 +1852,17 @@ mod tests {
use chrono::TimeZone; use chrono::TimeZone;
let mut mock = MockTestBackendHandler::new(); let mut mock = MockTestBackendHandler::new();
mock.expect_list_users().returning(|_| { mock.expect_list_users().returning(|_, _| {
Ok(vec![User { Ok(vec![UserAndGroups {
user_id: UserId::new("bob_1"), user: User {
email: "bob@bobmail.bob".to_string(), user_id: UserId::new("bob_1"),
display_name: "Bôb Böbberson".to_string(), email: "bob@bobmail.bob".to_string(),
first_name: "Bôb".to_string(), display_name: "Bôb Böbberson".to_string(),
last_name: "Böbberson".to_string(), first_name: "Bôb".to_string(),
..Default::default() last_name: "Böbberson".to_string(),
..Default::default()
},
groups: None,
}]) }])
}); });
mock.expect_list_groups() mock.expect_list_groups()

View File

@ -35,7 +35,7 @@ mockall::mock! {
} }
#[async_trait] #[async_trait]
impl BackendHandler for TestTcpBackendHandler { impl BackendHandler for TestTcpBackendHandler {
async fn list_users(&self, filters: Option<UserRequestFilter>) -> Result<Vec<User>>; async fn list_users(&self, filters: Option<UserRequestFilter>, get_groups: bool) -> Result<Vec<UserAndGroups>>;
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>; async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
async fn get_user_details(&self, user_id: &UserId) -> Result<User>; async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>; async fn get_group_details(&self, group_id: GroupId) -> Result<GroupIdAndName>;