From 6160f90b8c174cb12e93ca5f8e0092800105d2d8 Mon Sep 17 00:00:00 2001 From: RatCornu Date: Fri, 25 Apr 2025 21:12:03 +0200 Subject: [PATCH] Add direct bind support --- src/api/client/session.rs | 25 ++++++++++++++++--------- src/core/config/mod.rs | 29 +++++++++++++---------------- tuwunel-example.toml | 18 +++++++++--------- 3 files changed, 38 insertions(+), 34 deletions(-) diff --git a/src/api/client/session.rs b/src/api/client/session.rs index c1907e9e..3c15484f 100644 --- a/src/api/client/session.rs +++ b/src/api/client/session.rs @@ -91,16 +91,23 @@ async fn ldap_login( lowercased_user_id: &UserId, password: &str, ) -> Result { - debug!("Searching user in LDAP"); + let user_dn = match services.config.ldap.bind_dn.as_ref() { + | Some(bind_dn) if bind_dn.contains("{username}") => + bind_dn.replace("{username}", lowercased_user_id.localpart()), + | _ => { + debug!("Searching user in LDAP"); - let dns = services.users.search_ldap(user_id).await?; + let dns = services.users.search_ldap(user_id).await?; + if dns.len() >= 2 { + return Err!(Ldap("LDAP search returned two or more results")); + } - if dns.len() >= 2 { - return Err!(Ldap("LDAP search returned two or more results")); - } + let Some(user_dn) = dns.first() else { + return password_login(services, user_id, lowercased_user_id, password).await; + }; - let Some(user_dn) = dns.first() else { - return password_login(services, user_id, lowercased_user_id, password).await; + user_dn.clone() + }, }; // LDAP users are automatically created on first login attempt. This is a very @@ -111,16 +118,16 @@ async fn ldap_login( // password is reserved for deactivated accounts. The tuwunel password field // will never be read to login a LDAP user so it's not an issue. if !services.users.exists(lowercased_user_id).await { - debug!("Creating user {lowercased_user_id} from LDAP"); services .users .create(lowercased_user_id, Some("*"), Some("ldap")) .await?; } + debug!("{user_dn:?} {password:?}"); services .users - .auth_ldap(user_dn, password) + .auth_ldap(&user_dn, password) .await .map(|()| lowercased_user_id.to_owned()) } diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index 0bb1044f..7e0b2122 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -1805,14 +1805,7 @@ pub struct Config { #[serde(default)] pub blurhashing: BlurhashConfig, - #[cfg(not(doctest))] - /// Examples: - /// - /// - No LDAP login (default): - /// - /// ldap = "none" - /// - /// default: "none" + // external structure; separate section pub ldap: LdapConfig, #[serde(flatten)] @@ -1911,12 +1904,6 @@ pub struct LdapConfig { #[serde(deserialize_with = "crate::utils::deserialize_from_str")] pub uri: Url, - /// Whether to use StartTLS to bind to the LDAP server. - /// - /// example: true - #[serde(default)] - pub start_tls: bool, - /// Root of the searches. /// /// example: "ou=users,dc=example,dc=org" @@ -1925,7 +1912,13 @@ pub struct LdapConfig { /// Bind DN if anonymous search is not enabled. /// - /// example: "cn=ldap-reader,dc=example,dc=org" + /// You can use the variable `{username}` that will be replaced by the + /// entered username. In such case, the password used to bind will be the + /// one provided for the login and not the one given by + /// `bind_password_file`. + /// + /// example: "cn=ldap-reader,dc=example,dc=org" or + /// "cn={username},ou=users,dc=example,dc=org" #[serde(default)] pub bind_dn: Option, @@ -1955,12 +1948,16 @@ pub struct LdapConfig { /// Attribute containing the mail of the user. /// /// example: "mail" + /// + /// default: "mail" #[serde(default = "default_ldap_mail_attribute")] pub mail_attribute: String, /// Attribute containing the distinguished name of the user. /// /// example: "givenName" or "sn" + /// + /// default: "givenName" #[serde(default = "default_ldap_name_attribute")] pub name_attribute: String, } @@ -2359,4 +2356,4 @@ fn default_ldap_uid_attribute() -> String { String::from("uid") } fn default_ldap_mail_attribute() -> String { String::from("mail") } -fn default_ldap_name_attribute() -> String { String::from("name") } +fn default_ldap_name_attribute() -> String { String::from("givenName") } diff --git a/tuwunel-example.toml b/tuwunel-example.toml index 4a6c222e..d43a0f79 100644 --- a/tuwunel-example.toml +++ b/tuwunel-example.toml @@ -1641,12 +1641,6 @@ # #uri = -# Whether to use StartTLS to bind to the LDAP server. -# -# example: true -# -#start_tls = false - # Root of the searches. # # example: "ou=users,dc=example,dc=org" @@ -1655,7 +1649,13 @@ # Bind DN if anonymous search is not enabled. # -# example: "cn=ldap-reader,dc=example,dc=org" +# You can use the variable `{username}` that will be replaced by the +# entered username. In such case, the password used to bind will be the +# one provided for the login and not the one given by +# `bind_password_file`. +# +# example: "cn=ldap-reader,dc=example,dc=org" or +# "cn={username},ou=users,dc=example,dc=org" # #bind_dn = false @@ -1682,10 +1682,10 @@ # # example: "mail" # -#mail_attribute = +#mail_attribute = "mail" # Attribute containing the distinguished name of the user. # # example: "givenName" or "sn" # -#name_attribute = +#name_attribute = "givenName"