/* * 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 . * * 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::{Directory, Principal, Type}; use mail_send::Credentials; use crate::directory::parse_config; #[tokio::test] async fn sql_directory() { // Enable logging /*tracing::subscriber::set_global_default( tracing_subscriber::FmtSubscriber::builder() .with_max_level(tracing::Level::TRACE) .finish(), ) .unwrap();*/ // Obtain directory handle let mut config = parse_config(); let lookups = config.lookups; let handle = config.directories.remove("sql").unwrap(); // Create tables create_test_directory(handle.as_ref()).await; // Create test users create_test_user(handle.as_ref(), "john", "12345", "John Doe").await; create_test_user(handle.as_ref(), "jane", "abcde", "Jane Doe").await; create_test_user( handle.as_ref(), "bill", "$2y$05$bvIG6Nmid91Mu9RcmmWZfO5HJIMCT8riNW0hEp8f6/FuA2/mHZFpe", "Bill Foobar", ) .await; set_test_quota(handle.as_ref(), "bill", 500000).await; // Create test groups create_test_group(handle.as_ref(), "sales", "Sales Team").await; create_test_group(handle.as_ref(), "support", "Support Team").await; // Link users to groups add_to_group(handle.as_ref(), "john", "sales").await; add_to_group(handle.as_ref(), "jane", "sales").await; add_to_group(handle.as_ref(), "jane", "support").await; // Add email addresses link_test_address(handle.as_ref(), "john", "john@example.org", "primary").await; link_test_address(handle.as_ref(), "jane", "jane@example.org", "primary").await; link_test_address(handle.as_ref(), "bill", "bill@example.org", "primary").await; // Add aliases and lists link_test_address(handle.as_ref(), "john", "john.doe@example.org", "alias").await; link_test_address(handle.as_ref(), "john", "jdoe@example.org", "alias").await; link_test_address(handle.as_ref(), "john", "info@example.org", "list").await; link_test_address(handle.as_ref(), "jane", "info@example.org", "list").await; link_test_address(handle.as_ref(), "bill", "info@example.org", "list").await; // Add catch-all user create_test_user(handle.as_ref(), "robert", "abcde", "Robert Foobar").await; link_test_address(handle.as_ref(), "robert", "robert@catchall.org", "primary").await; link_test_address(handle.as_ref(), "robert", "@catchall.org", "alias").await; // Text lookup assert!(lookups .get("sql/domains") .unwrap() .contains("example.org") .await .unwrap()); // Test authentication assert_eq!( handle .authenticate(&Credentials::Plain { username: "john".to_string(), secret: "12345".to_string() }) .await .unwrap() .unwrap(), Principal { name: "john".to_string(), description: "John Doe".to_string().into(), secrets: vec!["12345".to_string()], typ: Type::Individual, member_of: vec!["sales".to_string()], ..Default::default() } ); assert_eq!( handle .authenticate(&Credentials::Plain { username: "bill".to_string(), secret: "password".to_string() }) .await .unwrap() .unwrap(), Principal { name: "bill".to_string(), description: "Bill Foobar".to_string().into(), secrets: vec![ "$2y$05$bvIG6Nmid91Mu9RcmmWZfO5HJIMCT8riNW0hEp8f6/FuA2/mHZFpe".to_string() ], typ: Type::Individual, quota: 500000, ..Default::default() } ); assert!(handle .authenticate(&Credentials::Plain { username: "bill".to_string(), secret: "invalid".to_string() }) .await .unwrap() .is_none()); // Get user by name assert_eq!( handle.principal("jane").await.unwrap().unwrap(), Principal { name: "jane".to_string(), description: "Jane Doe".to_string().into(), typ: Type::Individual, secrets: vec!["abcde".to_string()], member_of: vec!["sales".to_string(), "support".to_string()], ..Default::default() } ); // Get group by name assert_eq!( handle.principal("sales").await.unwrap().unwrap(), Principal { name: "sales".to_string(), description: "Sales Team".to_string().into(), typ: Type::Group, ..Default::default() } ); // Emails by id assert_eq!( handle.emails_by_name("john").await.unwrap(), vec![ "john@example.org".to_string(), "jdoe@example.org".to_string(), "john.doe@example.org".to_string(), ] ); assert_eq!( handle.emails_by_name("bill").await.unwrap(), vec!["bill@example.org".to_string(),] ); // Ids by email assert_eq!( handle.names_by_email("jane@example.org").await.unwrap(), vec!["jane".to_string()] ); assert_eq!( handle.names_by_email("info@example.org").await.unwrap(), vec!["bill".to_string(), "jane".to_string(), "john".to_string()] ); assert_eq!( handle .names_by_email("jane+alias@example.org") .await .unwrap(), vec!["jane".to_string()] ); assert_eq!( handle .names_by_email("info+alias@example.org") .await .unwrap(), vec!["bill".to_string(), "jane".to_string(), "john".to_string()] ); assert_eq!( handle.names_by_email("unknown@example.org").await.unwrap(), Vec::::new() ); assert_eq!( handle .names_by_email("anything@catchall.org") .await .unwrap(), vec!["robert".to_string()] ); // Domain validation assert!(handle.is_local_domain("example.org").await.unwrap()); assert!(!handle.is_local_domain("other.org").await.unwrap()); // RCPT TO assert!(handle.rcpt("jane@example.org").await.unwrap()); assert!(handle.rcpt("info@example.org").await.unwrap()); assert!(handle.rcpt("jane+alias@example.org").await.unwrap()); assert!(handle.rcpt("info+alias@example.org").await.unwrap()); assert!(handle.rcpt("random_user@catchall.org").await.unwrap()); assert!(!handle.rcpt("invalid@example.org").await.unwrap()); // VRFY assert_eq!( handle.vrfy("jane").await.unwrap(), vec!["jane@example.org".to_string()] ); assert_eq!( handle.vrfy("john").await.unwrap(), vec!["john@example.org".to_string()] ); assert_eq!( handle.vrfy("jane+alias@example").await.unwrap(), vec!["jane@example.org".to_string()] ); assert_eq!(handle.vrfy("info").await.unwrap(), Vec::::new()); assert_eq!(handle.vrfy("invalid").await.unwrap(), Vec::::new()); // EXPN assert_eq!( handle.expn("info@example.org").await.unwrap(), vec![ "bill@example.org".to_string(), "jane@example.org".to_string(), "john@example.org".to_string() ] ); assert_eq!( handle.expn("john@example.org").await.unwrap(), Vec::::new() ); } pub async fn create_test_directory(handle: &dyn Directory) { // Create tables for query in [ "CREATE TABLE accounts (name TEXT PRIMARY KEY, secret TEXT, description TEXT, type TEXT NOT NULL, quota INTEGER DEFAULT 0, active BOOLEAN DEFAULT 1)", "CREATE TABLE group_members (name TEXT NOT NULL, member_of TEXT NOT NULL, PRIMARY KEY (name, member_of))", "CREATE TABLE emails (name TEXT NOT NULL, address TEXT NOT NULL, type TEXT, PRIMARY KEY (name, address))", "INSERT INTO accounts (name, secret, type) VALUES ('admin', 'secret', 'individual')", ] { handle.query(query, &[]).await.unwrap_or_else(|_| panic!("failed for {query}")); } } pub async fn create_test_user(handle: &dyn Directory, login: &str, secret: &str, name: &str) { handle .query( "INSERT OR IGNORE INTO accounts (name, secret, description, type, active) VALUES (?, ?, ?, 'individual', true)", &[login, secret, name], ) .await .unwrap(); } pub async fn create_test_user_with_email( handle: &dyn Directory, login: &str, secret: &str, name: &str, ) { create_test_user(handle, login, secret, name).await; link_test_address(handle, login, login, "primary").await; } pub async fn create_test_group(handle: &dyn Directory, login: &str, name: &str) { handle .query( "INSERT OR IGNORE INTO accounts (name, description, type, active) VALUES (?, ?, 'group', true)", &[login, name], ) .await .unwrap(); } pub async fn create_test_group_with_email(handle: &dyn Directory, login: &str, name: &str) { create_test_group(handle, login, name).await; link_test_address(handle, login, login, "primary").await; } pub async fn link_test_address(handle: &dyn Directory, login: &str, address: &str, typ: &str) { handle .query( "INSERT OR IGNORE INTO emails (name, address, type) VALUES (?, ?, ?)", &[login, address, typ], ) .await .unwrap(); } pub async fn set_test_quota(handle: &dyn Directory, login: &str, quota: u32) { handle .query( &format!("UPDATE accounts SET quota = {} where name = ?", quota,), &[login], ) .await .unwrap(); } pub async fn add_to_group(handle: &dyn Directory, login: &str, group: &str) { handle .query( "INSERT INTO group_members (name, member_of) VALUES (?, ?)", &[login, group], ) .await .unwrap(); } pub async fn remove_from_group(handle: &dyn Directory, login: &str, group: &str) { handle .query( "DELETE FROM group_members WHERE name = ? AND member_of = ?", &[login, group], ) .await .unwrap(); } pub async fn remove_test_alias(handle: &dyn Directory, login: &str, alias: &str) { handle .query( "DELETE FROM emails WHERE name = ? AND address = ?", &[login, alias], ) .await .unwrap(); }