Files
Stalwart/tests/src/directory/ldap.rs

280 lines
8.1 KiB
Rust

/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd <hello@stalw.art>
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::fmt::Debug;
use directory::{
QueryBy, ROLE_USER, Type,
backend::{RcptType, internal::manage::ManageDirectory},
};
use mail_send::Credentials;
use crate::directory::{
DirectoryTest, IntoTestPrincipal, TestPrincipal, map_account_id, map_account_ids,
};
#[tokio::test]
async fn ldap_directory() {
// Enable logging
crate::enable_logging();
// Obtain directory handle
let mut config = DirectoryTest::new("sqlite".into()).await;
let handle = config.directories.directories.remove("ldap").unwrap();
let base_store = config.stores.stores.get("sqlite").unwrap();
let core = config.server;
// Test authentication
for (auth_type, handle) in [
("Default", handle.clone()),
(
"Bind template",
config
.directories
.directories
.remove("ldap-bind-template")
.unwrap(),
),
(
"Bind lookup",
config
.directories
.directories
.remove("ldap-bind-lookup")
.unwrap(),
),
] {
println!("Testing {auth_type} LDAP authentication...");
assert_eq!(
handle
.query(
QueryBy::Credentials(&Credentials::Plain {
username: "john".into(),
secret: "12345".into()
}),
true
)
.await
.unwrap()
.unwrap()
.into_test()
.into_sorted(),
TestPrincipal {
id: base_store.get_principal_id("john").await.unwrap().unwrap(),
name: "john".into(),
description: Some("John Doe".into()),
secrets: vec!["12345".into()],
typ: Type::Individual,
member_of: map_account_ids(base_store, vec!["sales"])
.await
.into_iter()
.map(|v| v.to_string())
.collect(),
emails: vec!["john@example.org".into(), "john.doe@example.org".into()],
roles: vec![ROLE_USER.to_string()],
..Default::default()
}
.into_sorted()
);
assert_eq!(
handle
.query(
QueryBy::Credentials(&Credentials::Plain {
username: "bill".into(),
secret: "password".into()
}),
true
)
.await
.unwrap()
.unwrap()
.into_test()
.into_sorted(),
TestPrincipal {
id: base_store.get_principal_id("bill").await.unwrap().unwrap(),
name: "bill".into(),
description: Some("Bill Foobar".into()),
secrets: vec![
"$2y$05$bvIG6Nmid91Mu9RcmmWZfO5HJIMCT8riNW0hEp8f6/FuA2/mHZFpe".into()
],
typ: Type::Individual,
quota: 500000,
emails: vec!["bill@example.org".into(),],
roles: vec![ROLE_USER.to_string()],
..Default::default()
}
.into_sorted()
);
assert!(
handle
.query(
QueryBy::Credentials(&Credentials::Plain {
username: "bill".into(),
secret: "invalid".into()
}),
true
)
.await
.unwrap()
.is_none()
);
}
// Get user by name
assert_eq!(
handle
.query(QueryBy::Name("jane"), true)
.await
.unwrap()
.unwrap()
.into_test()
.into_sorted(),
TestPrincipal {
id: base_store.get_principal_id("jane").await.unwrap().unwrap(),
name: "jane".into(),
description: Some("Jane Doe".into()),
typ: Type::Individual,
secrets: vec!["abcde".into()],
member_of: map_account_ids(base_store, vec!["sales", "support"])
.await
.into_iter()
.map(|v| v.to_string())
.collect(),
emails: vec!["jane@example.org".into(),],
roles: vec![ROLE_USER.to_string()],
..Default::default()
}
.into_sorted()
);
// Get group by name
assert_eq!(
handle
.query(QueryBy::Name("sales"), true)
.await
.unwrap()
.unwrap()
.into_test(),
TestPrincipal {
id: base_store.get_principal_id("sales").await.unwrap().unwrap(),
name: "sales".into(),
description: Some("sales".into()),
typ: Type::Group,
roles: vec![ROLE_USER.to_string()],
..Default::default()
}
);
// Ids by email
assert_eq!(
core.email_to_id(&handle, "jane@example.org", 0)
.await
.unwrap(),
Some(map_account_id(base_store, "jane").await),
);
assert_eq!(
core.email_to_id(&handle, "jane+alias@example.org", 0)
.await
.unwrap(),
Some(map_account_id(base_store, "jane").await),
);
assert_eq!(
core.email_to_id(&handle, "unknown@example.org", 0)
.await
.unwrap(),
None,
);
assert_eq!(
core.email_to_id(&handle, "anything@catchall.org", 0)
.await
.unwrap(),
Some(map_account_id(base_store, "robert").await)
);
// Domain validation
assert!(handle.is_local_domain("example.org").await.unwrap());
assert!(!handle.is_local_domain("other.org").await.unwrap());
// RCPT TO
assert_eq!(
core.rcpt(&handle, "jane@example.org", 0).await.unwrap(),
RcptType::Mailbox
);
assert_eq!(
core.rcpt(&handle, "info@example.org", 0).await.unwrap(),
RcptType::Mailbox
);
assert_eq!(
core.rcpt(&handle, "jane+alias@example.org", 0)
.await
.unwrap(),
RcptType::Mailbox
);
assert_eq!(
core.rcpt(&handle, "info+alias@example.org", 0)
.await
.unwrap(),
RcptType::Mailbox
);
assert_eq!(
core.rcpt(&handle, "random_user@catchall.org", 0)
.await
.unwrap(),
RcptType::Mailbox
);
assert_eq!(
core.rcpt(&handle, "invalid@example.org", 0).await.unwrap(),
RcptType::Invalid
);
// VRFY
compare_sorted(
core.vrfy(&handle, "jane", 0).await.unwrap(),
vec!["jane@example.org".into()],
);
compare_sorted(
core.vrfy(&handle, "john", 0).await.unwrap(),
vec!["john@example.org".into(), "john.doe@example.org".into()],
);
compare_sorted(
core.vrfy(&handle, "jane+alias@example", 0).await.unwrap(),
vec!["jane@example.org".into()],
);
compare_sorted(
core.vrfy(&handle, "info", 0).await.unwrap(),
Vec::<String>::new(),
);
compare_sorted(
core.vrfy(&handle, "invalid", 0).await.unwrap(),
Vec::<String>::new(),
);
// EXPN
// Now handled by the internal directory
/*compare_sorted(
core.expn(&handle, "info@example.org", 0).await.unwrap(),
vec![
"bill@example.org".into(),
"jane@example.org".into(),
"john@example.org".into(),
],
);
compare_sorted(
core.expn(&handle, "john@example.org", 0).await.unwrap(),
Vec::<String>::new(),
);*/
}
fn compare_sorted<T: Eq + Debug>(v1: Vec<T>, v2: Vec<T>) {
for val in v1.iter() {
assert!(v2.contains(val), "{v1:?} != {v2:?}");
}
for val in v2.iter() {
assert!(v1.contains(val), "{v1:?} != {v2:?}");
}
}