Internal directory implementation + Management REST API

This commit is contained in:
mdecimus
2023-12-16 10:35:15 +01:00
parent f21bce722e
commit 232d4d691a
64 changed files with 1933 additions and 621 deletions

View File

@@ -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(),

View 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())
);
}
}

View File

@@ -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
);

View File

@@ -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();

View File

@@ -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()

View File

@@ -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
);