Directory implementation - part 3
This commit is contained in:
116
tests/resources/ldap.cfg
Normal file
116
tests/resources/ldap.cfg
Normal file
@@ -0,0 +1,116 @@
|
||||
#################
|
||||
# LDAP test config
|
||||
|
||||
#################
|
||||
# General configuration.
|
||||
debug = true
|
||||
watchconfig = true
|
||||
|
||||
#################
|
||||
# Server configuration.
|
||||
[ldap]
|
||||
enabled = true
|
||||
# run on a non privileged port
|
||||
listen = "0.0.0.0:3893"
|
||||
|
||||
[ldaps]
|
||||
# to enable ldaps genrerate a certificate, eg. with:
|
||||
# openssl req -x509 -newkey rsa:4096 -keyout example.key -out example.crt -days 365 -nodes -subj '/CN=`hostname`'
|
||||
enabled = false
|
||||
listen = "0.0.0.0:3894"
|
||||
cert = "example.crt"
|
||||
key = "example.key"
|
||||
|
||||
#################
|
||||
# The backend section controls the data store.
|
||||
[backend]
|
||||
datastore = "config"
|
||||
baseDN = "dc=example,dc=org"
|
||||
nameformat = "cn"
|
||||
groupformat = "ou"
|
||||
|
||||
[behaviors]
|
||||
# Ignore all capabilities restrictions, for instance allowing every user to perform a search
|
||||
IgnoreCapabilities = false
|
||||
# Enable a "fail2ban" type backoff mechanism temporarily banning repeated failed login attempts
|
||||
LimitFailedBinds = true
|
||||
# How many failed login attempts are allowed before a ban is imposed
|
||||
NumberOfFailedBinds = 3
|
||||
# How long (in seconds) is the window for failed login attempts
|
||||
PeriodOfFailedBinds = 10
|
||||
# How long (in seconds) is the ban duration
|
||||
BlockFailedBindsFor = 60
|
||||
# Clean learnt IP addresses every N seconds
|
||||
PruneSourceTableEvery = 600
|
||||
# Clean learnt IP addresses not seen in N seconds
|
||||
PruneSourcesOlderThan = 600
|
||||
|
||||
#################
|
||||
# The users section contains a hardcoded list of valid users.
|
||||
[[users]]
|
||||
name = "john"
|
||||
givenname = "john.doe@example.org"
|
||||
sn = "info@example.org"
|
||||
uidnumber = 2
|
||||
primarygroup = 5
|
||||
mail = "john@example.org"
|
||||
[[users.customattributes]]
|
||||
principalName = ["John Doe"]
|
||||
userPassword = ["12345"]
|
||||
|
||||
[[users]]
|
||||
name = "jane"
|
||||
sn = "info@example.org"
|
||||
mail = "jane@example.org"
|
||||
uidnumber = 3
|
||||
primarygroup = 5
|
||||
[[users.customattributes]]
|
||||
otherGroups = ["support"]
|
||||
principalName = ["Jane Doe"]
|
||||
userPassword = ["abcde"]
|
||||
|
||||
[[users]]
|
||||
name = "bill"
|
||||
sn = "info@example.org"
|
||||
mail = "bill@example.org"
|
||||
uidnumber = 4
|
||||
[[users.customattributes]]
|
||||
principalName = ["Bill Foobar"]
|
||||
diskQuota = [500000]
|
||||
userPassword = ["$2y$05$bvIG6Nmid91Mu9RcmmWZfO5HJIMCT8riNW0hEp8f6/FuA2/mHZFpe"]
|
||||
|
||||
[[users]]
|
||||
name = "serviceuser"
|
||||
mail = "serviceuser@example.org"
|
||||
uidnumber = 5003
|
||||
primarygroup = 5502
|
||||
passsha256 = "652c7dc687d98c9889304ed2e408c74b611e86a40caa51c4b43f1dd5913c5cd0" # mysecret
|
||||
[[users.capabilities]]
|
||||
action = "search"
|
||||
object = "*"
|
||||
|
||||
|
||||
#################
|
||||
# The groups section contains a hardcoded list of valid users.
|
||||
[[groups]]
|
||||
name = "sales"
|
||||
gidnumber = 5
|
||||
|
||||
[[groups]]
|
||||
name = "support"
|
||||
gidnumber = 6
|
||||
|
||||
[[groups]]
|
||||
name = "svcaccts"
|
||||
gidnumber = 5502
|
||||
|
||||
|
||||
#################
|
||||
# Enable and configure the optional REST API here.
|
||||
[api]
|
||||
enabled = false
|
||||
internals = true # debug application performance
|
||||
tls = false # enable TLS for production!!
|
||||
listen = "0.0.0.0:5555"
|
||||
cert = "cert.pem"
|
||||
key = "key.pem"
|
||||
@@ -18,15 +18,15 @@ use super::dummy_tls_acceptor;
|
||||
#[tokio::test]
|
||||
async fn imap_directory() {
|
||||
// Enable logging
|
||||
tracing::subscriber::set_global_default(
|
||||
/*tracing::subscriber::set_global_default(
|
||||
tracing_subscriber::FmtSubscriber::builder()
|
||||
.with_max_level(tracing::Level::DEBUG)
|
||||
.finish(),
|
||||
)
|
||||
.unwrap();
|
||||
.unwrap();*/
|
||||
|
||||
// Obtain directory handle
|
||||
let handle = parse_config().remove("imap").unwrap();
|
||||
let handle = parse_config().directories.remove("imap").unwrap();
|
||||
|
||||
// Spawn mock LMTP server
|
||||
let shutdown = spawn_mock_imap_server(5);
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use directory::{Principal, Type};
|
||||
use mail_send::Credentials;
|
||||
|
||||
use crate::directory::parse_config;
|
||||
|
||||
#[tokio::test]
|
||||
async fn ldap_directory() {
|
||||
// Enable logging
|
||||
tracing::subscriber::set_global_default(
|
||||
tracing_subscriber::FmtSubscriber::builder()
|
||||
.with_max_level(tracing::Level::DEBUG)
|
||||
.finish(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Obtain directory handle
|
||||
let handle = parse_config().directories.remove("ldap").unwrap();
|
||||
|
||||
// Test authentication
|
||||
assert_eq!(
|
||||
handle
|
||||
.authenticate(&Credentials::Plain {
|
||||
username: "john".to_string(),
|
||||
secret: "12345".to_string()
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
Principal {
|
||||
id: 2,
|
||||
name: "john".to_string(),
|
||||
description: "John Doe".to_string().into(),
|
||||
secrets: vec!["12345".to_string()],
|
||||
typ: Type::Individual,
|
||||
member_of: vec!["ou=sales,ou=groups,dc=example,dc=org".to_string()],
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
handle
|
||||
.authenticate(&Credentials::Plain {
|
||||
username: "bill".to_string(),
|
||||
secret: "password".to_string()
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
Principal {
|
||||
id: 4,
|
||||
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 by id
|
||||
assert_eq!(
|
||||
handle.principal_by_id(2).await.unwrap().unwrap(),
|
||||
Principal {
|
||||
id: 2,
|
||||
name: "john".to_string(),
|
||||
description: "John Doe".to_string().into(),
|
||||
typ: Type::Individual,
|
||||
secrets: vec!["12345".to_string()],
|
||||
member_of: vec!["ou=sales,ou=groups,dc=example,dc=org".to_string()],
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
|
||||
// Get user by name
|
||||
let mut principal = handle.principal_by_name("jane").await.unwrap().unwrap();
|
||||
principal.member_of.sort_unstable();
|
||||
assert_eq!(
|
||||
principal,
|
||||
Principal {
|
||||
id: 3,
|
||||
name: "jane".to_string(),
|
||||
description: "Jane Doe".to_string().into(),
|
||||
typ: Type::Individual,
|
||||
secrets: vec!["abcde".to_string()],
|
||||
member_of: vec![
|
||||
"ou=sales,ou=groups,dc=example,dc=org".to_string(),
|
||||
"support".to_string()
|
||||
],
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
|
||||
// Get group by name
|
||||
assert_eq!(
|
||||
handle.principal_by_name("sales").await.unwrap().unwrap(),
|
||||
Principal {
|
||||
id: 5,
|
||||
name: "sales".to_string(),
|
||||
description: "sales".to_string().into(),
|
||||
typ: Type::Group,
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
|
||||
// Member of
|
||||
compare_sorted(
|
||||
handle
|
||||
.member_of(&handle.principal_by_name("john").await.unwrap().unwrap())
|
||||
.await
|
||||
.unwrap(),
|
||||
vec![5],
|
||||
);
|
||||
compare_sorted(
|
||||
handle
|
||||
.member_of(&handle.principal_by_name("jane").await.unwrap().unwrap())
|
||||
.await
|
||||
.unwrap(),
|
||||
vec![5, 6],
|
||||
);
|
||||
|
||||
// Emails by id
|
||||
compare_sorted(
|
||||
handle.emails_by_id(2).await.unwrap(),
|
||||
vec![
|
||||
"john@example.org".to_string(),
|
||||
"john.doe@example.org".to_string(),
|
||||
],
|
||||
);
|
||||
compare_sorted(
|
||||
handle.emails_by_id(4).await.unwrap(),
|
||||
vec!["bill@example.org".to_string()],
|
||||
);
|
||||
|
||||
// Ids by email
|
||||
compare_sorted(
|
||||
handle.ids_by_email("jane@example.org").await.unwrap(),
|
||||
vec![3],
|
||||
);
|
||||
compare_sorted(
|
||||
handle.ids_by_email("info@example.org").await.unwrap(),
|
||||
vec![2, 3, 4],
|
||||
);
|
||||
|
||||
// RCPT TO
|
||||
assert!(handle.rcpt("jane@example.org").await.unwrap());
|
||||
assert!(handle.rcpt("info@example.org").await.unwrap());
|
||||
assert!(!handle.rcpt("invalid@example.org").await.unwrap());
|
||||
|
||||
// VRFY
|
||||
compare_sorted(
|
||||
handle.vrfy("jane").await.unwrap(),
|
||||
vec!["jane@example.org".to_string()],
|
||||
);
|
||||
compare_sorted(
|
||||
handle.vrfy("john").await.unwrap(),
|
||||
vec!["john@example.org".to_string()],
|
||||
);
|
||||
compare_sorted(handle.vrfy("info").await.unwrap(), Vec::<String>::new());
|
||||
compare_sorted(handle.vrfy("invalid").await.unwrap(), Vec::<String>::new());
|
||||
|
||||
// EXPN
|
||||
compare_sorted(
|
||||
handle.expn("info@example.org").await.unwrap(),
|
||||
vec![
|
||||
"bill@example.org".to_string(),
|
||||
"jane@example.org".to_string(),
|
||||
"john@example.org".to_string(),
|
||||
],
|
||||
);
|
||||
compare_sorted(
|
||||
handle.expn("john@example.org").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:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@ pub mod ldap;
|
||||
pub mod smtp;
|
||||
pub mod sql;
|
||||
|
||||
use ahash::AHashMap;
|
||||
use directory::{config::ConfigDirectory, Directory};
|
||||
use directory::{config::ConfigDirectory, DirectoryConfig};
|
||||
use mail_send::Credentials;
|
||||
use rustls::{Certificate, PrivateKey, ServerConfig};
|
||||
use rustls_pemfile::{certs, pkcs8_private_keys};
|
||||
@@ -16,15 +15,18 @@ const CONFIG: &str = r#"
|
||||
protocol = "sql"
|
||||
address = "sqlite::memory:"
|
||||
|
||||
[directory."sql".pool]
|
||||
max-connections = 1
|
||||
|
||||
[directory."sql".query]
|
||||
login = "SELECT id, secret, description, quota FROM accounts WHERE name = ? AND active = true AND type = 'individual'"
|
||||
name = "SELECT id, type, description, quota FROM accounts WHERE name = ?"
|
||||
id = "SELECT name, type, description, quota FROM accounts WHERE id = ?"
|
||||
login = "SELECT id, name, type, secret, description, quota FROM accounts WHERE name = ? AND active = true AND type = 'individual'"
|
||||
name = "SELECT id, name, type, description, quota FROM accounts WHERE name = ?"
|
||||
id = "SELECT id, name, type, description, quota FROM accounts WHERE id = ?"
|
||||
members = "SELECT gid FROM group_members WHERE uid = ?"
|
||||
recipients = "SELECT id FROM emails WHERE address = ?"
|
||||
emails = "SELECT address FROM emails WHERE id = ? AND type != 'list' ORDER BY type DESC"
|
||||
verify = "SELECT address FROM emails WHERE address LIKE '%' || ? || '%' AND type != 'list' LIMIT 5"
|
||||
expand = "SELECT p.address FROM emails AS p JOIN emails AS l ON p.id = l.id WHERE p.type = 'primary' AND l.address = ? AND l.type = 'list' LIMIT 50"
|
||||
emails = "SELECT address FROM emails WHERE id = ? AND type != 'list' ORDER BY type DESC, address ASC"
|
||||
verify = "SELECT address FROM emails WHERE address LIKE '%' || ? || '%' AND type = 'primary' ORDER BY address LIMIT 5"
|
||||
expand = "SELECT p.address FROM emails AS p JOIN emails AS l ON p.id = l.id WHERE p.type = 'primary' AND l.address = ? AND l.type = 'list' ORDER BY p.address LIMIT 50"
|
||||
|
||||
[directory."sql".columns]
|
||||
name = "name"
|
||||
@@ -35,35 +37,41 @@ email = "address"
|
||||
quota = "quota"
|
||||
type = "type"
|
||||
|
||||
[directory."sql".lookup]
|
||||
domains = "SELECT name FROM domains WHERE name = ?"
|
||||
|
||||
[directory."ldap"]
|
||||
protocol = "ldap"
|
||||
address = "ldap://localhost:3893"
|
||||
base-dn = "dc=example,dc=com"
|
||||
base-dn = "dc=example,dc=org"
|
||||
|
||||
[directory."ldap".bind]
|
||||
dn = "cn=serviceuser,ou=svcaccts,dc=example,dc=com"
|
||||
dn = "cn=serviceuser,ou=svcaccts,dc=example,dc=org"
|
||||
secret = "mysecret"
|
||||
|
||||
[directory."ldap".filter]
|
||||
login = "(&(objectClass=posixAccount)(accountStatus=active)(cn=?))"
|
||||
name = "(&(!(objectClass=posixAccount)(objectClass=posixGroup))(cn=?))"
|
||||
email = "(&(!(objectClass=posixAccount)(objectClass=posixGroup))(!(mail=?)(mailAliases=?)(mailLists=?)))"
|
||||
name = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(cn=?))"
|
||||
email = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(|(mail=?)(givenName=?)(sn=?)))"
|
||||
id = "(|(&(objectClass=posixAccount)(uidNumber=?))(&(objectClass=posixGroup)(gidNumber=?)))"
|
||||
verify = "(&(!(objectClass=posixAccount)(objectClass=posixGroup))(!(mail=*?*)(mailAliases=*?*)))"
|
||||
expand = "(&(!(objectClass=posixAccount)(objectClass=posixGroup))(mailLists=?))"
|
||||
verify = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(|(mail=*?*)(givenName=*?*)))"
|
||||
expand = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(sn=?))"
|
||||
|
||||
[directory."ldap".object-classes]
|
||||
user = "posixAccount"
|
||||
group = "posixGroup"
|
||||
|
||||
# Glauth does not support searchable custom attributes so
|
||||
# 'sn' and 'givenName' are used to search for aliases/lists.
|
||||
|
||||
[directory."ldap".attributes]
|
||||
name = "cn"
|
||||
description = "description"
|
||||
description = ["principalName", "description"]
|
||||
secret = "userPassword"
|
||||
groups = "memberOf"
|
||||
groups = ["memberOf", "otherGroups"]
|
||||
id = ["uidNumber", "gidNumber"]
|
||||
email = "mail"
|
||||
email-alias = "mailAliases"
|
||||
email-alias = "givenName"
|
||||
quota = "diskQuota"
|
||||
|
||||
[directory."imap"]
|
||||
@@ -94,9 +102,47 @@ max-connections = 5
|
||||
implicit = true
|
||||
allow-invalid-certs = true
|
||||
|
||||
[directory."local"]
|
||||
protocol = "memory"
|
||||
|
||||
[[directory."local".users]]
|
||||
name = "john"
|
||||
description = "John Doe"
|
||||
secret = "12345"
|
||||
email = ["john@example.org", "jdoe@example.org", "john.doe@example.org"]
|
||||
email-list = ["info@example.org"]
|
||||
member-of = ["sales"]
|
||||
|
||||
[[directory."local".users]]
|
||||
name = "jane"
|
||||
description = "Jane Doe"
|
||||
secret = "abcde"
|
||||
email = "jane@example.org"
|
||||
email-list = ["info@example.org"]
|
||||
member-of = ["sales", "support"]
|
||||
|
||||
[[directory."local".users]]
|
||||
name = "bill"
|
||||
description = "Bill Foobar"
|
||||
secret = "$2y$05$bvIG6Nmid91Mu9RcmmWZfO5HJIMCT8riNW0hEp8f6/FuA2/mHZFpe"
|
||||
quota = 500000
|
||||
email = "bill@example.org"
|
||||
email-list = ["info@example.org"]
|
||||
|
||||
[[directory."local".groups]]
|
||||
name = "sales"
|
||||
description = "Sales Team"
|
||||
|
||||
[[directory."local".groups]]
|
||||
name = "support"
|
||||
description = "Support Team"
|
||||
|
||||
[directory."local".lookup]
|
||||
domains = ["example.org"]
|
||||
|
||||
"#;
|
||||
|
||||
pub fn parse_config() -> AHashMap<String, Arc<dyn Directory>> {
|
||||
pub fn parse_config() -> DirectoryConfig {
|
||||
utils::config::Config::parse(CONFIG)
|
||||
.unwrap()
|
||||
.parse_directory()
|
||||
|
||||
@@ -22,7 +22,7 @@ async fn smtp_directory() {
|
||||
let shutdown = spawn_mock_lmtp_server(5);
|
||||
|
||||
// Obtain directory handle
|
||||
let handle = parse_config().remove("smtp").unwrap();
|
||||
let handle = parse_config().directories.remove("smtp").unwrap();
|
||||
|
||||
// Basic lookup
|
||||
let tests = vec![
|
||||
|
||||
@@ -1,12 +1,221 @@
|
||||
use directory::Directory;
|
||||
use directory::{Directory, Principal, Type};
|
||||
use jmap_proto::types::id::Id;
|
||||
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::DEBUG)
|
||||
.finish(),
|
||||
)
|
||||
.unwrap();*/
|
||||
|
||||
// Obtain directory handle
|
||||
let handle = parse_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;
|
||||
|
||||
// Test authentication
|
||||
assert_eq!(
|
||||
handle
|
||||
.authenticate(&Credentials::Plain {
|
||||
username: "john".to_string(),
|
||||
secret: "12345".to_string()
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
Principal {
|
||||
id: 2,
|
||||
name: "john".to_string(),
|
||||
description: "John Doe".to_string().into(),
|
||||
secrets: vec!["12345".to_string()],
|
||||
typ: Type::Individual,
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
handle
|
||||
.authenticate(&Credentials::Plain {
|
||||
username: "bill".to_string(),
|
||||
secret: "password".to_string()
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
Principal {
|
||||
id: 4,
|
||||
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 by id
|
||||
assert_eq!(
|
||||
handle.principal_by_id(2).await.unwrap().unwrap(),
|
||||
Principal {
|
||||
id: 2,
|
||||
name: "john".to_string(),
|
||||
description: "John Doe".to_string().into(),
|
||||
typ: Type::Individual,
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
|
||||
// Get user by name
|
||||
assert_eq!(
|
||||
handle.principal_by_name("jane").await.unwrap().unwrap(),
|
||||
Principal {
|
||||
id: 3,
|
||||
name: "jane".to_string(),
|
||||
description: "Jane Doe".to_string().into(),
|
||||
typ: Type::Individual,
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
|
||||
// Get group by name
|
||||
assert_eq!(
|
||||
handle.principal_by_name("sales").await.unwrap().unwrap(),
|
||||
Principal {
|
||||
id: 5,
|
||||
name: "sales".to_string(),
|
||||
description: "Sales Team".to_string().into(),
|
||||
typ: Type::Group,
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
|
||||
// Member of
|
||||
assert_eq!(
|
||||
handle
|
||||
.member_of(&handle.principal_by_name("john").await.unwrap().unwrap())
|
||||
.await
|
||||
.unwrap(),
|
||||
vec![5]
|
||||
);
|
||||
assert_eq!(
|
||||
handle
|
||||
.member_of(&handle.principal_by_name("jane").await.unwrap().unwrap())
|
||||
.await
|
||||
.unwrap(),
|
||||
vec![5, 6]
|
||||
);
|
||||
|
||||
// Emails by id
|
||||
assert_eq!(
|
||||
handle.emails_by_id(2).await.unwrap(),
|
||||
vec![
|
||||
"john@example.org".to_string(),
|
||||
"jdoe@example.org".to_string(),
|
||||
"john.doe@example.org".to_string(),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
handle.emails_by_id(4).await.unwrap(),
|
||||
vec!["bill@example.org".to_string(),]
|
||||
);
|
||||
|
||||
// Ids by email
|
||||
assert_eq!(
|
||||
handle.ids_by_email("jane@example.org").await.unwrap(),
|
||||
vec![3]
|
||||
);
|
||||
assert_eq!(
|
||||
handle.ids_by_email("info@example.org").await.unwrap(),
|
||||
vec![2, 3, 4]
|
||||
);
|
||||
|
||||
// RCPT TO
|
||||
assert!(handle.rcpt("jane@example.org").await.unwrap());
|
||||
assert!(handle.rcpt("info@example.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("info").await.unwrap(), Vec::<String>::new());
|
||||
assert_eq!(handle.vrfy("invalid").await.unwrap(), Vec::<String>::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::<String>::new()
|
||||
);
|
||||
}
|
||||
|
||||
pub async fn create_test_directory(handle: &dyn Directory) {
|
||||
// Create tables
|
||||
for query in [
|
||||
"CREATE TABLE accounts (name TEXT, id INTEGER PRIMARY KEY, secret TEXT, description TEXT, type TEXT NOT NULL, quota INTEGER, active BOOLEAN DEFAULT 1)",
|
||||
"CREATE TABLE accounts (name TEXT, id INTEGER PRIMARY KEY, secret TEXT, description TEXT, type TEXT NOT NULL, quota INTEGER DEFAULT 0, active BOOLEAN DEFAULT 1)",
|
||||
"CREATE TABLE group_members (uid INTEGER, gid INTEGER, PRIMARY KEY (uid, gid))",
|
||||
"CREATE TABLE emails (id INTEGER NOT NULL, email TEXT NOT NULL, type TEXT, PRIMARY KEY (id, email))",
|
||||
"CREATE TABLE emails (id INTEGER NOT NULL, address TEXT NOT NULL, type TEXT, PRIMARY KEY (id, address))",
|
||||
"INSERT INTO accounts (name, secret, type) VALUES ('admin', 'secret', 'individual')",
|
||||
] {
|
||||
handle.query(query, &[]).await.unwrap_or_else(|_| panic!("failed for {query}"));
|
||||
@@ -16,13 +225,13 @@ pub async fn create_test_directory(handle: &dyn Directory) {
|
||||
pub async fn create_test_user(handle: &dyn Directory, login: &str, secret: &str, name: &str) -> Id {
|
||||
handle
|
||||
.query(
|
||||
"INSERT OR IGNORE INTO users (name, secret, description, type, is_active) VALUES (?, ?, ?, 'individual', true)",
|
||||
"INSERT OR IGNORE INTO accounts (name, secret, description, type, active) VALUES (?, ?, ?, 'individual', true)",
|
||||
&[login, secret, name],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Id::from(handle.principal_by_name(login).await.unwrap().unwrap().id)
|
||||
Id::from(get_principal_id(handle, login).await)
|
||||
}
|
||||
|
||||
pub async fn create_test_user_with_email(
|
||||
@@ -39,34 +248,27 @@ pub async fn create_test_user_with_email(
|
||||
pub async fn create_test_group(handle: &dyn Directory, login: &str, name: &str) -> Id {
|
||||
handle
|
||||
.query(
|
||||
"INSERT OR IGNORE INTO users (name, description, type, is_active) VALUES (?, ?, 'group', true)",
|
||||
"INSERT OR IGNORE INTO accounts (name, description, type, active) VALUES (?, ?, 'group', true)",
|
||||
&[login, name],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let id = handle.principal_by_name(login).await.unwrap().unwrap().id;
|
||||
Id::from(get_principal_id(handle, login).await)
|
||||
}
|
||||
|
||||
handle
|
||||
.query(
|
||||
&format!(
|
||||
"INSERT OR IGNORE INTO emails (id, email, type) VALUES ({}, ?, 'primary')",
|
||||
id
|
||||
),
|
||||
&[login],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Id::from(id)
|
||||
pub async fn create_test_group_with_email(handle: &dyn Directory, login: &str, name: &str) -> Id {
|
||||
let id = create_test_group(handle, login, name).await;
|
||||
link_test_address(handle, login, login, "primary").await;
|
||||
id
|
||||
}
|
||||
|
||||
pub async fn link_test_address(handle: &dyn Directory, login: &str, address: &str, typ: &str) {
|
||||
let id = handle.principal_by_name(login).await.unwrap().unwrap().id;
|
||||
let id = get_principal_id(handle, login).await;
|
||||
handle
|
||||
.query(
|
||||
&format!(
|
||||
"INSERT OR IGNORE INTO emails (id, email, type) VALUES ({}, ?, ?)",
|
||||
"INSERT OR IGNORE INTO emails (id, address, type) VALUES ({}, ?, ?)",
|
||||
id,
|
||||
),
|
||||
&[address, typ],
|
||||
@@ -75,7 +277,28 @@ pub async fn link_test_address(handle: &dyn Directory, login: &str, address: &st
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub async fn add_to_group(handle: &dyn Directory, uid: u32, gid: u32) {
|
||||
pub async fn set_test_quota(handle: &dyn Directory, login: &str, quota: u32) {
|
||||
let id = get_principal_id(handle, login).await;
|
||||
handle
|
||||
.query(
|
||||
&format!("UPDATE accounts SET quota = {} where id = {}", quota, id,),
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub async fn add_to_group(handle: &dyn Directory, login: &str, group: &str) {
|
||||
let user = handle.principal_by_name(login).await.unwrap().unwrap();
|
||||
let group = handle.principal_by_name(group).await.unwrap().unwrap();
|
||||
|
||||
let uid = user.id;
|
||||
let gid = group.id;
|
||||
|
||||
assert_ne!(uid, gid, "{user:?} {group:?}");
|
||||
assert_ne!(uid, u32::MAX, "{user:?} {group:?}");
|
||||
assert_ne!(gid, u32::MAX, "{user:?} {group:?}");
|
||||
|
||||
handle
|
||||
.query(
|
||||
&format!(
|
||||
@@ -99,12 +322,18 @@ pub async fn remove_from_group(handle: &dyn Directory, uid: u32, gid: u32) {
|
||||
}
|
||||
|
||||
pub async fn remove_test_alias(handle: &dyn Directory, login: &str, alias: &str) {
|
||||
let id = handle.principal_by_name(login).await.unwrap().unwrap().id;
|
||||
let id = get_principal_id(handle, login).await;
|
||||
handle
|
||||
.query(
|
||||
&format!("DELETE FROM emails WHERE id = {} AND email = ?", id),
|
||||
&format!("DELETE FROM emails WHERE id = {} AND address = ?", id),
|
||||
&[alias],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn get_principal_id(handle: &dyn Directory, name: &str) -> u32 {
|
||||
let p = handle.principal_by_name(name).await.unwrap().unwrap();
|
||||
assert_ne!(p.id, u32::MAX, "{name} {p:#?}");
|
||||
p.id
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user