LDAP authentication enhancements (closes #1269 closes #1471 closes #795 closes #1496)

This commit is contained in:
mdecimus
2025-06-02 16:17:56 +02:00
parent 01ca8e2e3b
commit d49e6e1945
11 changed files with 421 additions and 194 deletions

View File

@@ -74,6 +74,7 @@ watchconfig = true
sn = "info@example.org"
mail = "bill@example.org"
uidnumber = 4
passsha256 = "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
[[users.customattributes]]
principalName = ["Bill Foobar"]
diskQuota = [500000]

View File

@@ -19,12 +19,7 @@ use crate::directory::{
#[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();*/
crate::enable_logging();
// Obtain directory handle
let mut config = DirectoryTest::new("sqlite".into()).await;
@@ -33,77 +28,100 @@ async fn ldap_directory() {
let core = config.server;
// Test authentication
assert_eq!(
handle
.query(
QueryBy::Credentials(&Credentials::Plain {
username: "john".into(),
secret: "12345".into()
}),
true
)
.await
.unwrap()
.unwrap()
.into_test()
.into_sorted(),
TestPrincipal {
id: base_store.get_principal_id("john").await.unwrap().unwrap(),
name: "john".into(),
description: Some("John Doe".into()),
secrets: vec!["12345".into()],
typ: Type::Individual,
member_of: map_account_ids(base_store, vec!["sales"])
for (auth_type, handle) in [
("Default", handle.clone()),
(
"Bind template",
config
.directories
.directories
.remove("ldap-bind-template")
.unwrap(),
),
(
"Bind lookup",
config
.directories
.directories
.remove("ldap-bind-lookup")
.unwrap(),
),
] {
println!("Testing {auth_type} LDAP authentication...");
assert_eq!(
handle
.query(
QueryBy::Credentials(&Credentials::Plain {
username: "john".into(),
secret: "12345".into()
}),
true
)
.await
.into_iter()
.map(|v| v.to_string())
.collect(),
emails: vec!["john@example.org".into(), "john.doe@example.org".into()],
roles: vec![ROLE_USER.to_string()],
..Default::default()
}
.into_sorted()
);
assert_eq!(
handle
.query(
QueryBy::Credentials(&Credentials::Plain {
username: "bill".into(),
secret: "password".into()
}),
true
)
.await
.unwrap()
.unwrap()
.into_test()
.into_sorted(),
TestPrincipal {
id: base_store.get_principal_id("bill").await.unwrap().unwrap(),
name: "bill".into(),
description: Some("Bill Foobar".into()),
secrets: vec!["$2y$05$bvIG6Nmid91Mu9RcmmWZfO5HJIMCT8riNW0hEp8f6/FuA2/mHZFpe".into()],
typ: Type::Individual,
quota: 500000,
emails: vec!["bill@example.org".into(),],
roles: vec![ROLE_USER.to_string()],
..Default::default()
}
.into_sorted()
);
assert!(
handle
.query(
QueryBy::Credentials(&Credentials::Plain {
username: "bill".into(),
secret: "invalid".into()
}),
true
)
.await
.unwrap()
.is_none()
);
.unwrap()
.unwrap()
.into_test()
.into_sorted(),
TestPrincipal {
id: base_store.get_principal_id("john").await.unwrap().unwrap(),
name: "john".into(),
description: Some("John Doe".into()),
secrets: vec!["12345".into()],
typ: Type::Individual,
member_of: map_account_ids(base_store, vec!["sales"])
.await
.into_iter()
.map(|v| v.to_string())
.collect(),
emails: vec!["john@example.org".into(), "john.doe@example.org".into()],
roles: vec![ROLE_USER.to_string()],
..Default::default()
}
.into_sorted()
);
assert_eq!(
handle
.query(
QueryBy::Credentials(&Credentials::Plain {
username: "bill".into(),
secret: "password".into()
}),
true
)
.await
.unwrap()
.unwrap()
.into_test()
.into_sorted(),
TestPrincipal {
id: base_store.get_principal_id("bill").await.unwrap().unwrap(),
name: "bill".into(),
description: Some("Bill Foobar".into()),
secrets: vec![
"$2y$05$bvIG6Nmid91Mu9RcmmWZfO5HJIMCT8riNW0hEp8f6/FuA2/mHZFpe".into()
],
typ: Type::Individual,
quota: 500000,
emails: vec!["bill@example.org".into(),],
roles: vec![ROLE_USER.to_string()],
..Default::default()
}
.into_sorted()
);
assert!(
handle
.query(
QueryBy::Credentials(&Credentials::Plain {
username: "bill".into(),
secret: "invalid".into()
}),
true
)
.await
.unwrap()
.is_none()
);
}
// Get user by name
assert_eq!(

View File

@@ -143,19 +143,9 @@ base-dn = "dc=example,dc=org"
dn = "cn=serviceuser,ou=svcaccts,dc=example,dc=org"
secret = "mysecret"
[directory."ldap".bind.auth]
enable = false
dn = "cn=?,ou=svcaccts,dc=example,dc=org"
[directory."ldap".filter]
name = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(uid=?))"
email = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(|(mail=?)(givenName=?)(sn=?)))"
verify = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(|(mail=*?*)(givenName=*?*)))"
expand = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(sn=?))"
domains = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(|(mail=*@?)(givenName=*@?)(sn=*@?)))"
# Glauth does not support searchable custom attributes so
# 'sn' and 'givenName' are used to search for aliases/lists.
[directory."ldap".attributes]
name = "uid"
@@ -167,6 +157,60 @@ email-alias = "givenName"
quota = "diskQuota"
class = "objectClass"
[directory."ldap-bind-template"]
type = "ldap"
url = "ldap://localhost:3893"
base-dn = "dc=example,dc=org"
[directory."ldap-bind-template".bind]
dn = "cn=serviceuser,ou=svcaccts,dc=example,dc=org"
secret = "mysecret"
[directory."ldap-bind-template".bind.auth]
method = "template"
template = "cn={username},ou=,dc=example,dc=org"
search = false
[directory."ldap-bind-template".filter]
name = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(uid=?))"
email = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(|(mail=?)(givenName=?)(sn=?)))"
[directory."ldap-bind-template".attributes]
name = "uid"
description = ["principalName", "description"]
secret = "userPassword"
groups = ["memberOf", "otherGroups"]
email = "mail"
email-alias = "givenName"
quota = "diskQuota"
class = "objectClass"
[directory."ldap-bind-lookup"]
type = "ldap"
url = "ldap://localhost:3893"
base-dn = "dc=example,dc=org"
[directory."ldap-bind-lookup".bind]
dn = "cn=serviceuser,ou=svcaccts,dc=example,dc=org"
secret = "mysecret"
[directory."ldap-bind-lookup".bind.auth]
method = "lookup"
[directory."ldap-bind-lookup".filter]
name = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(uid=?))"
email = "(&(|(objectClass=posixAccount)(objectClass=posixGroup))(|(mail=?)(givenName=?)(sn=?)))"
[directory."ldap-bind-lookup".attributes]
name = "uid"
description = ["principalName", "description"]
secret = "userPassword"
groups = ["memberOf", "otherGroups"]
email = "mail"
email-alias = "givenName"
quota = "diskQuota"
class = "objectClass"
##############################################################################
[directory."imap"]