Internal directory implementation + Management REST API
This commit is contained in:
@@ -35,7 +35,7 @@ use tokio_rustls::TlsAcceptor;
|
||||
|
||||
use utils::listener::limiter::{ConcurrencyLimiter, InFlight};
|
||||
|
||||
use crate::directory::{parse_config, Item, LookupResult};
|
||||
use crate::directory::{DirectoryTest, Item, LookupResult};
|
||||
|
||||
use super::dummy_tls_acceptor;
|
||||
|
||||
@@ -54,7 +54,7 @@ async fn imap_directory() {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
|
||||
// Obtain directory handle
|
||||
let mut config = parse_config().await;
|
||||
let mut config = DirectoryTest::new(None).await;
|
||||
let handle = config.directories.directories.remove("imap").unwrap();
|
||||
|
||||
// Basic lookup
|
||||
@@ -79,7 +79,7 @@ async fn imap_directory() {
|
||||
assert_eq!(
|
||||
&LookupResult::from(
|
||||
handle
|
||||
.query(QueryBy::credentials(item.as_credentials()))
|
||||
.query(QueryBy::Credentials(item.as_credentials()))
|
||||
.await
|
||||
.unwrap()
|
||||
.is_some()
|
||||
@@ -99,7 +99,7 @@ async fn imap_directory() {
|
||||
tokio::spawn(async move {
|
||||
LookupResult::from(
|
||||
handle
|
||||
.query(QueryBy::credentials(item.as_credentials()))
|
||||
.query(QueryBy::Credentials(item.as_credentials()))
|
||||
.await
|
||||
.unwrap()
|
||||
.is_some(),
|
||||
|
||||
590
tests/src/directory/internal.rs
Normal file
590
tests/src/directory/internal.rs
Normal file
@@ -0,0 +1,590 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Stalwart Labs Ltd.
|
||||
*
|
||||
* This file is part of Stalwart Mail Server.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
* in the LICENSE file at the top-level directory of this distribution.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* You can be released from the requirements of the AGPLv3 license by
|
||||
* purchasing a commercial license. Please contact licensing@stalw.art
|
||||
* for more details.
|
||||
*/
|
||||
|
||||
use directory::{
|
||||
backend::internal::{manage::ManageDirectory, PrincipalField, PrincipalUpdate, PrincipalValue},
|
||||
Directory, DirectoryError, ManagementError, Principal, QueryBy, Type,
|
||||
};
|
||||
use jmap_proto::types::collection::Collection;
|
||||
use mail_send::Credentials;
|
||||
use store::{
|
||||
roaring::RoaringBitmap,
|
||||
write::{BatchBuilder, BitmapClass, ValueClass},
|
||||
BitmapKey, ValueKey,
|
||||
};
|
||||
|
||||
use crate::directory::DirectoryTest;
|
||||
|
||||
#[tokio::test]
|
||||
async fn internal_directory() {
|
||||
let config = DirectoryTest::new(None).await;
|
||||
|
||||
for (store_id, store) in config.stores.stores {
|
||||
println!("Testing internal directory with store {:?}", store_id);
|
||||
store.destroy().await;
|
||||
|
||||
// A principal without name should fail
|
||||
assert_eq!(
|
||||
store.create_account(Principal::default()).await,
|
||||
Err(DirectoryError::Management(ManagementError::MissingField(
|
||||
PrincipalField::Name
|
||||
)))
|
||||
);
|
||||
|
||||
// Basic account creation
|
||||
assert_eq!(
|
||||
store
|
||||
.create_account(Principal {
|
||||
name: "john".to_string(),
|
||||
description: Some("John Doe".to_string()),
|
||||
secrets: vec!["secret".to_string(), "secret2".to_string()],
|
||||
..Default::default()
|
||||
})
|
||||
.await,
|
||||
Ok(0)
|
||||
);
|
||||
|
||||
// Two accounts with the same name should fail
|
||||
assert_eq!(
|
||||
store
|
||||
.create_account(Principal {
|
||||
name: "john".to_string(),
|
||||
..Default::default()
|
||||
})
|
||||
.await,
|
||||
Err(DirectoryError::Management(ManagementError::NotUniqueField(
|
||||
PrincipalField::Name
|
||||
)))
|
||||
);
|
||||
|
||||
// An account using a non-existent domain should fail
|
||||
assert_eq!(
|
||||
store
|
||||
.create_account(Principal {
|
||||
name: "jane".to_string(),
|
||||
emails: vec!["jane@example.org".to_string()],
|
||||
..Default::default()
|
||||
})
|
||||
.await,
|
||||
Err(DirectoryError::Management(ManagementError::NotFound(
|
||||
"example.org".to_string()
|
||||
)))
|
||||
);
|
||||
|
||||
// Create a domain name
|
||||
assert_eq!(store.create_domain("example.org").await, Ok(()));
|
||||
assert!(store.is_local_domain("example.org").await.unwrap());
|
||||
assert!(!store.is_local_domain("otherdomain.org").await.unwrap());
|
||||
|
||||
// Add an email address
|
||||
assert_eq!(
|
||||
store
|
||||
.update_account(
|
||||
QueryBy::Name("john"),
|
||||
vec![PrincipalUpdate::add_item(
|
||||
PrincipalField::Emails,
|
||||
PrincipalValue::String("john@example.org".to_string()),
|
||||
)],
|
||||
)
|
||||
.await,
|
||||
Ok(())
|
||||
);
|
||||
assert!(store.rcpt("john@example.org").await.unwrap());
|
||||
assert_eq!(
|
||||
store.email_to_ids("john@example.org").await.unwrap(),
|
||||
vec![0]
|
||||
);
|
||||
|
||||
// Using non-existent domain should fail
|
||||
assert_eq!(
|
||||
store
|
||||
.update_account(
|
||||
QueryBy::Name("john"),
|
||||
vec![PrincipalUpdate::add_item(
|
||||
PrincipalField::Emails,
|
||||
PrincipalValue::String("john@otherdomain.org".to_string()),
|
||||
)],
|
||||
)
|
||||
.await,
|
||||
Err(DirectoryError::Management(ManagementError::NotFound(
|
||||
"otherdomain.org".to_string()
|
||||
)))
|
||||
);
|
||||
|
||||
// Create an account with an email address
|
||||
assert_eq!(
|
||||
store
|
||||
.create_account(Principal {
|
||||
name: "jane".to_string(),
|
||||
description: Some("Jane Doe".to_string()),
|
||||
secrets: vec!["my_secret".to_string(), "my_secret2".to_string()],
|
||||
emails: vec!["jane@example.org".to_string()],
|
||||
quota: 123,
|
||||
..Default::default()
|
||||
})
|
||||
.await,
|
||||
Ok(1)
|
||||
);
|
||||
assert!(store.rcpt("jane@example.org").await.unwrap());
|
||||
assert!(!store.rcpt("jane@otherdomain.org").await.unwrap());
|
||||
assert_eq!(
|
||||
store.email_to_ids("jane@example.org").await.unwrap(),
|
||||
vec![1]
|
||||
);
|
||||
assert_eq!(store.vrfy("jane").await.unwrap(), vec!["jane@example.org"]);
|
||||
assert_eq!(
|
||||
store
|
||||
.query(QueryBy::Credentials(&Credentials::new(
|
||||
"jane".to_string(),
|
||||
"my_secret".to_string()
|
||||
)))
|
||||
.await
|
||||
.unwrap(),
|
||||
Some(Principal {
|
||||
name: "jane".to_string(),
|
||||
description: Some("Jane Doe".to_string()),
|
||||
emails: vec!["jane@example.org".to_string()],
|
||||
secrets: vec!["my_secret".to_string(), "my_secret2".to_string()],
|
||||
quota: 123,
|
||||
..Default::default()
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
store
|
||||
.query(QueryBy::Credentials(&Credentials::new(
|
||||
"jane".to_string(),
|
||||
"wrong_password".to_string()
|
||||
)))
|
||||
.await
|
||||
.unwrap(),
|
||||
None
|
||||
);
|
||||
|
||||
// Duplicate email address should fail
|
||||
assert_eq!(
|
||||
store
|
||||
.create_account(Principal {
|
||||
name: "janeth".to_string(),
|
||||
description: Some("Janeth Doe".to_string()),
|
||||
emails: vec!["jane@example.org".to_string()],
|
||||
..Default::default()
|
||||
})
|
||||
.await,
|
||||
Err(DirectoryError::Management(ManagementError::NotUniqueField(
|
||||
PrincipalField::Emails
|
||||
)))
|
||||
);
|
||||
|
||||
// Create a mailing list
|
||||
assert_eq!(
|
||||
store
|
||||
.create_account(Principal {
|
||||
name: "list".to_string(),
|
||||
typ: Type::List,
|
||||
emails: vec!["list@example.org".to_string()],
|
||||
member_of: vec!["john".to_string(), "jane".to_string()],
|
||||
..Default::default()
|
||||
})
|
||||
.await,
|
||||
Ok(2)
|
||||
);
|
||||
assert!(store.rcpt("list@example.org").await.unwrap());
|
||||
assert_eq!(
|
||||
store.email_to_ids("list@example.org").await.unwrap(),
|
||||
vec![0, 1]
|
||||
);
|
||||
assert_eq!(
|
||||
store
|
||||
.map_group_ids(store.query(QueryBy::Name("list")).await.unwrap().unwrap())
|
||||
.await
|
||||
.unwrap(),
|
||||
Principal {
|
||||
name: "list".to_string(),
|
||||
typ: Type::List,
|
||||
emails: vec!["list@example.org".to_string()],
|
||||
member_of: vec!["john".to_string(), "jane".to_string()],
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
store.expn("list@example.org").await.unwrap(),
|
||||
vec!["john@example.org", "jane@example.org"]
|
||||
);
|
||||
|
||||
// Create groups
|
||||
assert_eq!(
|
||||
store
|
||||
.create_account(Principal {
|
||||
name: "sales".to_string(),
|
||||
description: Some("Sales Team".to_string()),
|
||||
typ: Type::Group,
|
||||
..Default::default()
|
||||
})
|
||||
.await,
|
||||
Ok(3)
|
||||
);
|
||||
assert_eq!(
|
||||
store
|
||||
.create_account(Principal {
|
||||
name: "support".to_string(),
|
||||
description: Some("Support Team".to_string()),
|
||||
typ: Type::Group,
|
||||
..Default::default()
|
||||
})
|
||||
.await,
|
||||
Ok(4)
|
||||
);
|
||||
|
||||
// Add John to the Sales and Support groups
|
||||
assert_eq!(
|
||||
store
|
||||
.update_account(
|
||||
QueryBy::Name("john"),
|
||||
vec![
|
||||
PrincipalUpdate::add_item(
|
||||
PrincipalField::MemberOf,
|
||||
PrincipalValue::String("sales".to_string()),
|
||||
),
|
||||
PrincipalUpdate::add_item(
|
||||
PrincipalField::MemberOf,
|
||||
PrincipalValue::String("support".to_string()),
|
||||
)
|
||||
],
|
||||
)
|
||||
.await,
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
store
|
||||
.map_group_ids(store.query(QueryBy::Name("john")).await.unwrap().unwrap())
|
||||
.await
|
||||
.unwrap(),
|
||||
Principal {
|
||||
name: "john".to_string(),
|
||||
description: Some("John Doe".to_string()),
|
||||
secrets: vec!["secret".to_string(), "secret2".to_string()],
|
||||
emails: vec!["john@example.org".to_string()],
|
||||
member_of: vec!["sales".to_string(), "support".to_string()],
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
|
||||
// Adding a non-existent user should fail
|
||||
assert_eq!(
|
||||
store
|
||||
.update_account(
|
||||
QueryBy::Name("john"),
|
||||
vec![PrincipalUpdate::add_item(
|
||||
PrincipalField::MemberOf,
|
||||
PrincipalValue::String("accounting".to_string()),
|
||||
)],
|
||||
)
|
||||
.await,
|
||||
Err(DirectoryError::Management(ManagementError::NotFound(
|
||||
"accounting".to_string()
|
||||
)))
|
||||
);
|
||||
|
||||
// Remove a member from a group
|
||||
assert_eq!(
|
||||
store
|
||||
.update_account(
|
||||
QueryBy::Name("john"),
|
||||
vec![PrincipalUpdate::remove_item(
|
||||
PrincipalField::MemberOf,
|
||||
PrincipalValue::String("support".to_string()),
|
||||
)],
|
||||
)
|
||||
.await,
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
store
|
||||
.map_group_ids(store.query(QueryBy::Name("john")).await.unwrap().unwrap())
|
||||
.await
|
||||
.unwrap(),
|
||||
Principal {
|
||||
name: "john".to_string(),
|
||||
description: Some("John Doe".to_string()),
|
||||
secrets: vec!["secret".to_string(), "secret2".to_string()],
|
||||
emails: vec!["john@example.org".to_string()],
|
||||
member_of: vec!["sales".to_string()],
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
|
||||
// Update multiple fields
|
||||
assert_eq!(
|
||||
store
|
||||
.update_account(
|
||||
QueryBy::Name("john"),
|
||||
vec![
|
||||
PrincipalUpdate::set(
|
||||
PrincipalField::Name,
|
||||
PrincipalValue::String("john.doe".to_string())
|
||||
),
|
||||
PrincipalUpdate::set(
|
||||
PrincipalField::Description,
|
||||
PrincipalValue::String("Johnny Doe".to_string())
|
||||
),
|
||||
PrincipalUpdate::set(
|
||||
PrincipalField::Secrets,
|
||||
PrincipalValue::StringList(vec!["12345".to_string()])
|
||||
),
|
||||
PrincipalUpdate::set(PrincipalField::Quota, PrincipalValue::Integer(1024)),
|
||||
PrincipalUpdate::set(
|
||||
PrincipalField::Type,
|
||||
PrincipalValue::Type(Type::Superuser)
|
||||
),
|
||||
PrincipalUpdate::remove_item(
|
||||
PrincipalField::Emails,
|
||||
PrincipalValue::String("john@example.org".to_string()),
|
||||
),
|
||||
PrincipalUpdate::add_item(
|
||||
PrincipalField::Emails,
|
||||
PrincipalValue::String("john.doe@example.org".to_string()),
|
||||
)
|
||||
],
|
||||
)
|
||||
.await,
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
store
|
||||
.map_group_ids(
|
||||
store
|
||||
.query(QueryBy::Name("john.doe"))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
Principal {
|
||||
name: "john.doe".to_string(),
|
||||
description: Some("Johnny Doe".to_string()),
|
||||
secrets: vec!["12345".to_string()],
|
||||
emails: vec!["john.doe@example.org".to_string()],
|
||||
quota: 1024,
|
||||
typ: Type::Superuser,
|
||||
member_of: vec!["sales".to_string()],
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
assert_eq!(store.get_account_id("john").await.unwrap(), None);
|
||||
assert!(!store.rcpt("john@example.org").await.unwrap());
|
||||
assert!(store.rcpt("john.doe@example.org").await.unwrap());
|
||||
|
||||
// Remove a member from a mailing list and then add it back
|
||||
assert_eq!(
|
||||
store
|
||||
.update_account(
|
||||
QueryBy::Name("list"),
|
||||
vec![PrincipalUpdate::remove_item(
|
||||
PrincipalField::MemberOf,
|
||||
PrincipalValue::String("john.doe".to_string()),
|
||||
)],
|
||||
)
|
||||
.await,
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
store
|
||||
.map_group_ids(store.query(QueryBy::Name("list")).await.unwrap().unwrap())
|
||||
.await
|
||||
.unwrap(),
|
||||
Principal {
|
||||
name: "list".to_string(),
|
||||
typ: Type::List,
|
||||
emails: vec!["list@example.org".to_string()],
|
||||
member_of: vec!["jane".to_string()],
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
store
|
||||
.update_account(
|
||||
QueryBy::Name("list"),
|
||||
vec![PrincipalUpdate::add_item(
|
||||
PrincipalField::MemberOf,
|
||||
PrincipalValue::String("john.doe".to_string()),
|
||||
)],
|
||||
)
|
||||
.await,
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
store
|
||||
.map_group_ids(store.query(QueryBy::Name("list")).await.unwrap().unwrap())
|
||||
.await
|
||||
.unwrap(),
|
||||
Principal {
|
||||
name: "list".to_string(),
|
||||
typ: Type::List,
|
||||
emails: vec!["list@example.org".to_string()],
|
||||
member_of: vec!["jane".to_string(), "john.doe".to_string()],
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
|
||||
// Field validation
|
||||
assert_eq!(
|
||||
store
|
||||
.update_account(
|
||||
QueryBy::Name("john.doe"),
|
||||
vec![PrincipalUpdate::set(
|
||||
PrincipalField::Name,
|
||||
PrincipalValue::String("jane".to_string())
|
||||
),],
|
||||
)
|
||||
.await,
|
||||
Err(DirectoryError::Management(ManagementError::NotUniqueField(
|
||||
PrincipalField::Name
|
||||
)))
|
||||
);
|
||||
assert_eq!(
|
||||
store
|
||||
.update_account(
|
||||
QueryBy::Name("john.doe"),
|
||||
vec![PrincipalUpdate::add_item(
|
||||
PrincipalField::Emails,
|
||||
PrincipalValue::String("jane@example.org".to_string())
|
||||
),],
|
||||
)
|
||||
.await,
|
||||
Err(DirectoryError::Management(ManagementError::NotUniqueField(
|
||||
PrincipalField::Emails
|
||||
)))
|
||||
);
|
||||
|
||||
// List accounts
|
||||
assert_eq!(
|
||||
store.list_accounts(None, 0).await.unwrap(),
|
||||
vec!["jane", "john.doe", "list", "sales", "support"]
|
||||
);
|
||||
assert_eq!(
|
||||
store.list_accounts("john".into(), 2).await.unwrap(),
|
||||
vec!["john.doe", "list"]
|
||||
);
|
||||
|
||||
// Write records on John's and Jane's accounts
|
||||
for account_id in [0, 1] {
|
||||
let document_id = store
|
||||
.assign_document_id(account_id, Collection::Email)
|
||||
.await
|
||||
.unwrap();
|
||||
store
|
||||
.write(
|
||||
BatchBuilder::new()
|
||||
.with_account_id(account_id)
|
||||
.with_collection(Collection::Email)
|
||||
.create_document(document_id)
|
||||
.set(ValueClass::Property(0), "hello".as_bytes())
|
||||
.build_batch(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
store
|
||||
.get_value::<String>(ValueKey {
|
||||
account_id,
|
||||
collection: Collection::Email.into(),
|
||||
document_id,
|
||||
class: ValueClass::Property(0)
|
||||
})
|
||||
.await
|
||||
.unwrap(),
|
||||
Some("hello".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
// Delete John's account and make sure his records are gone
|
||||
store.delete_account(QueryBy::Id(0)).await.unwrap();
|
||||
assert_eq!(store.get_account_id("john.doe").await.unwrap(), None);
|
||||
assert_eq!(
|
||||
store.email_to_ids("john.doe@example.org").await.unwrap(),
|
||||
Vec::<u32>::new()
|
||||
);
|
||||
assert!(!store.rcpt("john.doe@example.org").await.unwrap());
|
||||
assert_eq!(
|
||||
store.list_accounts(None, 0).await.unwrap(),
|
||||
vec!["jane", "list", "sales", "support"]
|
||||
);
|
||||
assert_eq!(
|
||||
store
|
||||
.get_bitmap(BitmapKey {
|
||||
account_id: 0,
|
||||
collection: Collection::Email.into(),
|
||||
class: BitmapClass::DocumentIds,
|
||||
block_num: 0
|
||||
})
|
||||
.await
|
||||
.unwrap(),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
store
|
||||
.get_value::<String>(ValueKey {
|
||||
account_id: 0,
|
||||
collection: Collection::Email.into(),
|
||||
document_id: 0,
|
||||
class: ValueClass::Property(0)
|
||||
})
|
||||
.await
|
||||
.unwrap(),
|
||||
None
|
||||
);
|
||||
|
||||
// Make sure Jane's records are still there
|
||||
assert_eq!(store.get_account_id("jane").await.unwrap(), Some(1));
|
||||
assert_eq!(
|
||||
store.email_to_ids("jane@example.org").await.unwrap(),
|
||||
vec![1]
|
||||
);
|
||||
assert!(store.rcpt("jane@example.org").await.unwrap());
|
||||
assert_eq!(
|
||||
store
|
||||
.get_bitmap(BitmapKey {
|
||||
account_id: 1,
|
||||
collection: Collection::Email.into(),
|
||||
class: BitmapClass::DocumentIds,
|
||||
block_num: 0
|
||||
})
|
||||
.await
|
||||
.unwrap(),
|
||||
Some(RoaringBitmap::from_sorted_iter([0]).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
store
|
||||
.get_value::<String>(ValueKey {
|
||||
account_id: 1,
|
||||
collection: Collection::Email.into(),
|
||||
document_id: 0,
|
||||
class: ValueClass::Property(0)
|
||||
})
|
||||
.await
|
||||
.unwrap(),
|
||||
Some("hello".to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ use std::fmt::Debug;
|
||||
use directory::{backend::internal::manage::ManageDirectory, Principal, QueryBy, Type};
|
||||
use mail_send::Credentials;
|
||||
|
||||
use crate::directory::{map_account_ids, parse_config, IntoSortedPrincipal};
|
||||
use crate::directory::{map_account_ids, DirectoryTest, IntoSortedPrincipal};
|
||||
|
||||
#[tokio::test]
|
||||
async fn ldap_directory() {
|
||||
@@ -39,20 +39,17 @@ async fn ldap_directory() {
|
||||
.unwrap();*/
|
||||
|
||||
// Obtain directory handle
|
||||
let mut config = parse_config().await;
|
||||
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();
|
||||
|
||||
// Test authentication
|
||||
assert_eq!(
|
||||
handle
|
||||
.query(
|
||||
QueryBy::credentials(&Credentials::Plain {
|
||||
username: "john".to_string(),
|
||||
secret: "12345".to_string()
|
||||
})
|
||||
.with_store(base_store)
|
||||
)
|
||||
.query(QueryBy::Credentials(&Credentials::Plain {
|
||||
username: "john".to_string(),
|
||||
secret: "12345".to_string()
|
||||
}))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
@@ -74,13 +71,10 @@ async fn ldap_directory() {
|
||||
);
|
||||
assert_eq!(
|
||||
handle
|
||||
.query(
|
||||
QueryBy::credentials(&Credentials::Plain {
|
||||
username: "bill".to_string(),
|
||||
secret: "password".to_string()
|
||||
})
|
||||
.with_store(base_store)
|
||||
)
|
||||
.query(QueryBy::Credentials(&Credentials::Plain {
|
||||
username: "bill".to_string(),
|
||||
secret: "password".to_string()
|
||||
}))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
@@ -100,13 +94,10 @@ async fn ldap_directory() {
|
||||
.into_sorted()
|
||||
);
|
||||
assert!(handle
|
||||
.query(
|
||||
QueryBy::credentials(&Credentials::Plain {
|
||||
username: "bill".to_string(),
|
||||
secret: "invalid".to_string()
|
||||
})
|
||||
.with_store(base_store)
|
||||
)
|
||||
.query(QueryBy::Credentials(&Credentials::Plain {
|
||||
username: "bill".to_string(),
|
||||
secret: "invalid".to_string()
|
||||
}))
|
||||
.await
|
||||
.unwrap()
|
||||
.is_none());
|
||||
@@ -114,7 +105,7 @@ async fn ldap_directory() {
|
||||
// Get user by name
|
||||
assert_eq!(
|
||||
handle
|
||||
.query(QueryBy::name("jane").with_store(base_store))
|
||||
.query(QueryBy::Name("jane"))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
@@ -134,11 +125,7 @@ async fn ldap_directory() {
|
||||
|
||||
// Get group by name
|
||||
assert_eq!(
|
||||
handle
|
||||
.query(QueryBy::name("sales").with_store(base_store))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
handle.query(QueryBy::Name("sales")).await.unwrap().unwrap(),
|
||||
Principal {
|
||||
id: base_store.get_account_id("sales").await.unwrap().unwrap(),
|
||||
name: "sales".to_string(),
|
||||
@@ -150,45 +137,27 @@ async fn ldap_directory() {
|
||||
|
||||
// Ids by email
|
||||
compare_sorted(
|
||||
handle
|
||||
.email_to_ids("jane@example.org", base_store)
|
||||
.await
|
||||
.unwrap(),
|
||||
handle.email_to_ids("jane@example.org").await.unwrap(),
|
||||
map_account_ids(base_store, vec!["jane"]).await,
|
||||
);
|
||||
compare_sorted(
|
||||
handle
|
||||
.email_to_ids("jane+alias@example.org", base_store)
|
||||
.await
|
||||
.unwrap(),
|
||||
handle.email_to_ids("jane+alias@example.org").await.unwrap(),
|
||||
map_account_ids(base_store, vec!["jane"]).await,
|
||||
);
|
||||
compare_sorted(
|
||||
handle
|
||||
.email_to_ids("info@example.org", base_store)
|
||||
.await
|
||||
.unwrap(),
|
||||
handle.email_to_ids("info@example.org").await.unwrap(),
|
||||
map_account_ids(base_store, vec!["bill", "jane", "john"]).await,
|
||||
);
|
||||
compare_sorted(
|
||||
handle
|
||||
.email_to_ids("info+alias@example.org", base_store)
|
||||
.await
|
||||
.unwrap(),
|
||||
handle.email_to_ids("info+alias@example.org").await.unwrap(),
|
||||
map_account_ids(base_store, vec!["bill", "jane", "john"]).await,
|
||||
);
|
||||
compare_sorted(
|
||||
handle
|
||||
.email_to_ids("unknown@example.org", base_store)
|
||||
.await
|
||||
.unwrap(),
|
||||
handle.email_to_ids("unknown@example.org").await.unwrap(),
|
||||
Vec::<u32>::new(),
|
||||
);
|
||||
assert_eq!(
|
||||
handle
|
||||
.email_to_ids("anything@catchall.org", base_store)
|
||||
.await
|
||||
.unwrap(),
|
||||
handle.email_to_ids("anything@catchall.org").await.unwrap(),
|
||||
map_account_ids(base_store, vec!["robert"]).await
|
||||
);
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
*/
|
||||
|
||||
pub mod imap;
|
||||
pub mod internal;
|
||||
pub mod ldap;
|
||||
pub mod smtp;
|
||||
pub mod sql;
|
||||
@@ -274,16 +275,18 @@ pub struct DirectoryTest {
|
||||
pub temp_dir: TempDir,
|
||||
}
|
||||
|
||||
pub async fn parse_config() -> DirectoryTest {
|
||||
let temp_dir = TempDir::new("directory_tests", true);
|
||||
let config_file = CONFIG.replace("{TMP}", &temp_dir.path.to_string_lossy());
|
||||
let config = utils::config::Config::new(&config_file).unwrap();
|
||||
let stores = config.parse_stores().await.unwrap();
|
||||
impl DirectoryTest {
|
||||
pub async fn new(id_store: Option<&str>) -> DirectoryTest {
|
||||
let temp_dir = TempDir::new("directory_tests", true);
|
||||
let config_file = CONFIG.replace("{TMP}", &temp_dir.path.to_string_lossy());
|
||||
let config = utils::config::Config::new(&config_file).unwrap();
|
||||
let stores = config.parse_stores().await.unwrap();
|
||||
|
||||
DirectoryTest {
|
||||
directories: config.parse_directory(&stores).unwrap(),
|
||||
stores,
|
||||
temp_dir,
|
||||
DirectoryTest {
|
||||
directories: config.parse_directory(&stores, id_store).unwrap(),
|
||||
stores,
|
||||
temp_dir,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -625,7 +628,7 @@ trait IntoSortedPrincipal: Sized {
|
||||
fn into_sorted(self) -> Self;
|
||||
}
|
||||
|
||||
impl IntoSortedPrincipal for Principal {
|
||||
impl IntoSortedPrincipal for Principal<u32> {
|
||||
fn into_sorted(mut self) -> Self {
|
||||
self.member_of.sort_unstable();
|
||||
self.emails.sort_unstable();
|
||||
|
||||
@@ -35,7 +35,7 @@ use tokio_rustls::TlsAcceptor;
|
||||
|
||||
use utils::listener::limiter::{ConcurrencyLimiter, InFlight};
|
||||
|
||||
use crate::directory::{parse_config, Item, LookupResult};
|
||||
use crate::directory::{DirectoryTest, Item, LookupResult};
|
||||
|
||||
use super::dummy_tls_acceptor;
|
||||
|
||||
@@ -46,7 +46,7 @@ async fn smtp_directory() {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
|
||||
// Obtain directory handle
|
||||
let mut config = parse_config().await;
|
||||
let mut config = DirectoryTest::new(None).await;
|
||||
let handle = config.directories.directories.remove("smtp").unwrap();
|
||||
|
||||
// Basic lookup
|
||||
@@ -97,7 +97,7 @@ async fn smtp_directory() {
|
||||
let result: LookupResult = match item {
|
||||
Item::IsAccount(v) => handle.rcpt(v).await.unwrap().into(),
|
||||
Item::Authenticate(v) => handle
|
||||
.query(QueryBy::credentials(v))
|
||||
.query(QueryBy::Credentials(v))
|
||||
.await
|
||||
.unwrap()
|
||||
.is_some()
|
||||
@@ -129,7 +129,7 @@ async fn smtp_directory() {
|
||||
let result: LookupResult = match &item {
|
||||
Item::IsAccount(v) => handle.rcpt(v).await.unwrap().into(),
|
||||
Item::Authenticate(v) => handle
|
||||
.query(QueryBy::credentials(v))
|
||||
.query(QueryBy::Credentials(v))
|
||||
.await
|
||||
.unwrap()
|
||||
.is_some()
|
||||
|
||||
@@ -27,7 +27,7 @@ use mail_send::Credentials;
|
||||
use smtp::core::Lookup;
|
||||
use store::{LookupStore, Store};
|
||||
|
||||
use crate::directory::{map_account_ids, parse_config};
|
||||
use crate::directory::{map_account_ids, DirectoryTest};
|
||||
|
||||
use super::DirectoryStore;
|
||||
|
||||
@@ -41,17 +41,17 @@ async fn sql_directory() {
|
||||
)
|
||||
.unwrap();*/
|
||||
|
||||
// Parse config
|
||||
let mut config = parse_config().await;
|
||||
let lookups = config
|
||||
.stores
|
||||
.lookups
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, Lookup::from(v)))
|
||||
.collect::<AHashMap<_, _>>();
|
||||
|
||||
// Obtain directory handle
|
||||
for directory_id in ["sqlite", "postgresql", "mysql"] {
|
||||
// Parse config
|
||||
let mut config = DirectoryTest::new(directory_id.into()).await;
|
||||
let lookups = config
|
||||
.stores
|
||||
.lookups
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, Lookup::from(v)))
|
||||
.collect::<AHashMap<_, _>>();
|
||||
|
||||
println!("Testing SQL directory {:?}", directory_id);
|
||||
let handle = config.directories.directories.remove(directory_id).unwrap();
|
||||
let store = DirectoryStore {
|
||||
@@ -133,13 +133,10 @@ async fn sql_directory() {
|
||||
// Test authentication
|
||||
assert_eq!(
|
||||
handle
|
||||
.query(
|
||||
QueryBy::credentials(&Credentials::Plain {
|
||||
username: "john".to_string(),
|
||||
secret: "12345".to_string()
|
||||
})
|
||||
.with_store(base_store)
|
||||
)
|
||||
.query(QueryBy::Credentials(&Credentials::Plain {
|
||||
username: "john".to_string(),
|
||||
secret: "12345".to_string()
|
||||
}))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
@@ -160,13 +157,10 @@ async fn sql_directory() {
|
||||
);
|
||||
assert_eq!(
|
||||
handle
|
||||
.query(
|
||||
QueryBy::credentials(&Credentials::Plain {
|
||||
username: "bill".to_string(),
|
||||
secret: "password".to_string()
|
||||
})
|
||||
.with_store(base_store)
|
||||
)
|
||||
.query(QueryBy::Credentials(&Credentials::Plain {
|
||||
username: "bill".to_string(),
|
||||
secret: "password".to_string()
|
||||
}))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
@@ -184,24 +178,17 @@ async fn sql_directory() {
|
||||
}
|
||||
);
|
||||
assert!(handle
|
||||
.query(
|
||||
QueryBy::credentials(&Credentials::Plain {
|
||||
username: "bill".to_string(),
|
||||
secret: "invalid".to_string()
|
||||
})
|
||||
.with_store(base_store)
|
||||
)
|
||||
.query(QueryBy::Credentials(&Credentials::Plain {
|
||||
username: "bill".to_string(),
|
||||
secret: "invalid".to_string()
|
||||
}))
|
||||
.await
|
||||
.unwrap()
|
||||
.is_none());
|
||||
|
||||
// Get user by name
|
||||
assert_eq!(
|
||||
handle
|
||||
.query(QueryBy::name("jane").with_store(base_store))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
handle.query(QueryBy::Name("jane")).await.unwrap().unwrap(),
|
||||
Principal {
|
||||
id: base_store.get_account_id("jane").await.unwrap().unwrap(),
|
||||
name: "jane".to_string(),
|
||||
@@ -216,11 +203,7 @@ async fn sql_directory() {
|
||||
|
||||
// Get group by name
|
||||
assert_eq!(
|
||||
handle
|
||||
.query(QueryBy::name("sales").with_store(base_store))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
handle.query(QueryBy::Name("sales")).await.unwrap().unwrap(),
|
||||
Principal {
|
||||
id: base_store.get_account_id("sales").await.unwrap().unwrap(),
|
||||
name: "sales".to_string(),
|
||||
@@ -232,45 +215,27 @@ async fn sql_directory() {
|
||||
|
||||
// Ids by email
|
||||
assert_eq!(
|
||||
handle
|
||||
.email_to_ids("jane@example.org", base_store)
|
||||
.await
|
||||
.unwrap(),
|
||||
handle.email_to_ids("jane@example.org").await.unwrap(),
|
||||
map_account_ids(base_store, vec!["jane"]).await
|
||||
);
|
||||
assert_eq!(
|
||||
handle
|
||||
.email_to_ids("info@example.org", base_store)
|
||||
.await
|
||||
.unwrap(),
|
||||
handle.email_to_ids("info@example.org").await.unwrap(),
|
||||
map_account_ids(base_store, vec!["bill", "jane", "john"]).await
|
||||
);
|
||||
assert_eq!(
|
||||
handle
|
||||
.email_to_ids("jane+alias@example.org", base_store)
|
||||
.await
|
||||
.unwrap(),
|
||||
handle.email_to_ids("jane+alias@example.org").await.unwrap(),
|
||||
map_account_ids(base_store, vec!["jane"]).await
|
||||
);
|
||||
assert_eq!(
|
||||
handle
|
||||
.email_to_ids("info+alias@example.org", base_store)
|
||||
.await
|
||||
.unwrap(),
|
||||
handle.email_to_ids("info+alias@example.org").await.unwrap(),
|
||||
map_account_ids(base_store, vec!["bill", "jane", "john"]).await
|
||||
);
|
||||
assert_eq!(
|
||||
handle
|
||||
.email_to_ids("unknown@example.org", base_store)
|
||||
.await
|
||||
.unwrap(),
|
||||
handle.email_to_ids("unknown@example.org").await.unwrap(),
|
||||
Vec::<u32>::new()
|
||||
);
|
||||
assert_eq!(
|
||||
handle
|
||||
.email_to_ids("anything@catchall.org", base_store)
|
||||
.await
|
||||
.unwrap(),
|
||||
handle.email_to_ids("anything@catchall.org").await.unwrap(),
|
||||
map_account_ids(base_store, vec!["robert"]).await
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user