Automatically create Inbox for group accounts

This commit is contained in:
mdecimus 2023-08-06 17:55:35 +02:00
parent a0ceaeba1c
commit 80f6a5469a
8 changed files with 128 additions and 51 deletions

View file

@ -73,43 +73,8 @@ impl MemoryDirectory {
member_of,
},
);
let mut emails = Vec::new();
for (pos, (_, email)) in config
.values((prefix.as_str(), "users", lookup_id, "email"))
.enumerate()
{
directory
.emails_to_names
.entry(email.to_string())
.or_default()
.push(if pos > 0 {
EmailType::Alias(name.clone())
} else {
EmailType::Primary(name.clone())
});
if let Some((_, domain)) = email.rsplit_once('@') {
directory.domains.insert(domain.to_lowercase());
}
emails.push(if pos > 0 {
EmailType::Alias(email.to_lowercase())
} else {
EmailType::Primary(email.to_lowercase())
});
}
for (_, email) in config.values((prefix.as_str(), "users", lookup_id, "email-list")) {
directory
.emails_to_names
.entry(email.to_lowercase())
.or_default()
.push(EmailType::List(name.clone()));
if let Some((_, domain)) = email.rsplit_once('@') {
directory.domains.insert(domain.to_lowercase());
}
emails.push(EmailType::List(email.to_lowercase()));
}
directory.names_to_email.insert(name, emails);
directory.parse_emails(config, (prefix.as_str(), "users", lookup_id), name)?;
}
for lookup_id in config.sub_keys((prefix.as_str(), "groups")) {
@ -119,7 +84,7 @@ impl MemoryDirectory {
directory.principals.insert(
name.clone(),
Principal {
name,
name: name.clone(),
secrets: vec![],
typ: Type::Group,
description: config
@ -134,6 +99,8 @@ impl MemoryDirectory {
.collect(),
},
);
directory.parse_emails(config, (prefix.as_str(), "groups", lookup_id), name)?;
}
directory
@ -143,3 +110,49 @@ impl MemoryDirectory {
Ok(Arc::new(directory))
}
}
impl MemoryDirectory {
fn parse_emails(
&mut self,
config: &Config,
prefix: impl AsKey,
name: String,
) -> utils::config::Result<()> {
let prefix = prefix.as_key();
let mut emails = Vec::new();
for (pos, (_, email)) in config.values((prefix.as_str(), "email")).enumerate() {
self.emails_to_names
.entry(email.to_string())
.or_default()
.push(if pos > 0 {
EmailType::Alias(name.clone())
} else {
EmailType::Primary(name.clone())
});
if let Some((_, domain)) = email.rsplit_once('@') {
self.domains.insert(domain.to_lowercase());
}
emails.push(if pos > 0 {
EmailType::Alias(email.to_lowercase())
} else {
EmailType::Primary(email.to_lowercase())
});
}
for (_, email) in config.values((prefix.as_str(), "email-list")) {
self.emails_to_names
.entry(email.to_lowercase())
.or_default()
.push(EmailType::List(name.clone()));
if let Some((_, domain)) = email.rsplit_once('@') {
self.domains.insert(domain.to_lowercase());
}
emails.push(EmailType::List(email.to_lowercase()));
}
self.names_to_email.insert(name, emails);
Ok(())
}
}

View file

@ -28,7 +28,7 @@ use crate::{DirectoryOptions, Principal};
pub mod config;
pub mod lookup;
#[derive(Default)]
#[derive(Default, Debug)]
pub struct MemoryDirectory {
principals: AHashMap<String, Principal>,
emails_to_names: AHashMap<String, Vec<EmailType>>,
@ -37,6 +37,7 @@ pub struct MemoryDirectory {
opt: DirectoryOptions,
}
#[derive(Debug)]
enum EmailType {
Primary(String),
Alias(String),

View file

@ -81,17 +81,13 @@ impl SessionData {
mailbox_prefix: Option<String>,
access_token: &AccessToken,
) -> crate::Result<Account> {
let mailbox_ids = if access_token.is_primary_id(account_id) {
let mailbox_ids = if access_token.is_primary_id(account_id)
|| access_token.member_of.contains(&account_id)
{
self.jmap
.mailbox_get_or_create(account_id)
.await
.map_err(|_| {})?
} else if access_token.member_of.contains(&account_id) {
self.jmap
.get_document_ids(account_id, Collection::Mailbox)
.await
.map_err(|_| {})?
.unwrap_or_default()
} else {
self.jmap
.shared_documents(access_token, account_id, Collection::Mailbox, Acl::Read)

View file

@ -57,8 +57,7 @@ pub fn spawn_writer(mut stream: Event, span: tracing::Span) -> mpsc::Sender<Even
data = std::str::from_utf8(bytes.as_ref()).unwrap_or_default(),
size = bytes.len()
);
/*let tmp = "dd";
println!(
/*let c = println!(
"<- {:?}",
String::from_utf8_lossy(
&bytes[..std::cmp::min(bytes.len(), 100)]

View file

@ -176,10 +176,12 @@ email-list = ["info@example.org"]
[[directory."local".groups]]
name = "sales"
email = "sales@example.org"
description = "Sales Team"
[[directory."local".groups]]
name = "support"
email = "support@example.org"
description = "Support Team"
[oauth]

View file

@ -23,9 +23,27 @@
use imap_proto::ResponseType;
use crate::jmap::delivery::SmtpConnection;
use super::{append::assert_append_message, AssertResult, ImapConnection, Type};
pub async fn test(mut imap_john: &mut ImapConnection, _imap_check: &mut ImapConnection) {
// Delivery to support account
let mut lmtp = SmtpConnection::connect_port(11201).await;
lmtp.ingest(
"bill@example.com",
&["support@example.com"],
concat!(
"From: bill@example.com\r\n",
"To: support@example.com\r\n",
"Subject: TPS Report\r\n",
"\r\n",
"I'm going to need those TPS reports ASAP. ",
"So, if you could do that, that'd be great."
),
)
.await;
// Connect to all test accounts
let mut imap_jane = ImapConnection::connect(b"_w ").await;
let mut imap_bill = ImapConnection::connect(b"_z ").await;
@ -43,6 +61,25 @@ pub async fn test(mut imap_john: &mut ImapConnection, _imap_check: &mut ImapConn
imap.assert_read(Type::Tagged, ResponseType::Ok).await;
}
// Jane should see the Support account
imap_jane.send("LIST \"\" \"*\"").await;
imap_jane
.assert_read(Type::Tagged, ResponseType::Ok)
.await
.assert_contains("Shared Folders/support@example.com/Inbox");
imap_jane
.send("SELECT \"Shared Folders/support@example.com/Inbox\"")
.await;
imap_jane.assert_read(Type::Tagged, ResponseType::Ok).await;
imap_jane.send("FETCH 1 (PREVIEW)").await;
imap_jane
.assert_read(Type::Tagged, ResponseType::Ok)
.await
.assert_contains("TPS reports ASAP");
imap_jane.send("UNSELECT").await;
imap_jane.assert_read(Type::Tagged, ResponseType::Ok).await;
// John should have no shared folders
imap_john.send("LIST \"\" \"*\"").await;
imap_john

View file

@ -42,7 +42,7 @@ use directory::config::ConfigDirectory;
use imap::core::{ImapSessionManager, IMAP};
use imap_proto::ResponseType;
use jmap::{api::JmapSessionManager, services::IPC_CHANNEL_BUFFER, JMAP};
use smtp::core::SMTP;
use smtp::core::{SmtpSessionManager, SMTP};
use tokio::{
io::{AsyncBufReadExt, AsyncWriteExt, BufReader, Lines, ReadHalf, WriteHalf},
net::TcpStream,
@ -53,7 +53,8 @@ use utils::{config::ServerProtocol, UnwrapFailure};
use crate::{
add_test_certs,
directory::sql::{
add_to_group, create_test_directory, create_test_user, create_test_user_with_email,
add_to_group, create_test_directory, create_test_group_with_email, create_test_user,
create_test_user_with_email,
},
store::TempDir,
};
@ -79,6 +80,12 @@ protocol = "managesieve"
max-connections = 81920
tls.implicit = true
[server.listener.lmtp-debug]
bind = ['127.0.0.1:11201']
greeting = 'Test LMTP instance'
protocol = 'lmtp'
tls.implicit = false
[server.socket]
reuse-addr = true
@ -260,6 +267,9 @@ async fn init_imap_tests(delete_if_exists: bool) -> IMAPTest {
ManageSieveSessionManager::new(jmap.clone(), imap.clone()),
shutdown_rx,
),
ServerProtocol::Smtp | ServerProtocol::Lmtp => {
server.spawn(SmtpSessionManager::new(smtp.clone()), shutdown_rx)
}
_ => unreachable!(),
};
});
@ -289,6 +299,18 @@ async fn init_imap_tests(delete_if_exists: bool) -> IMAPTest {
"Bill Foobar",
)
.await;
create_test_group_with_email(
jmap.directory.as_ref(),
"support@example.com",
"Support Group",
)
.await;
add_to_group(
jmap.directory.as_ref(),
"jane.smith@example.com",
"support@example.com",
)
.await;
if delete_if_exists {
jmap.store.destroy().await;

View file

@ -295,8 +295,15 @@ impl SmtpConnection {
}
pub async fn connect() -> Self {
let (reader, writer) =
tokio::io::split(TcpStream::connect("127.0.0.1:11200").await.unwrap());
SmtpConnection::connect_port(11200).await
}
pub async fn connect_port(port: u16) -> Self {
let (reader, writer) = tokio::io::split(
TcpStream::connect(&format!("127.0.0.1:{port}"))
.await
.unwrap(),
);
let mut conn = SmtpConnection {
reader: BufReader::new(reader).lines(),
writer,