diff --git a/lldap_config.docker_template.toml b/lldap_config.docker_template.toml index 8dccb5d..cb2715e 100644 --- a/lldap_config.docker_template.toml +++ b/lldap_config.docker_template.toml @@ -78,6 +78,14 @@ database_url = "sqlite:///data/users.db?mode=rwc" ## Randomly generated on first run if it doesn't exist. key_file = "/data/private_key" +## Ignored attributes. +## Some services will request attributes that are not present in LLDAP. When it +## is the case, LLDAP will warn about the attribute being unknown. If you want +## to ignore the attribute and the service works without, you can add it to this +## list to silence the warning. +#ignored_user_attributes = [ "sAMAccountName" ] +#ignored_group_attributes = [ "mail", "userPrincipalName" ] + ## Options to configure SMTP parameters, to send password reset emails. ## To set these options from environment variables, use the following format ## (example with "password"): LLDAP_SMTP_OPTIONS__PASSWORD diff --git a/server/src/infra/configuration.rs b/server/src/infra/configuration.rs index 224ad2c..4b751b0 100644 --- a/server/src/infra/configuration.rs +++ b/server/src/infra/configuration.rs @@ -75,6 +75,10 @@ pub struct Configuration { pub ldap_user_pass: SecUtf8, #[builder(default = r#"String::from("sqlite://users.db?mode=rwc")"#)] pub database_url: String, + #[builder(default)] + pub ignored_user_attributes: Vec, + #[builder(default)] + pub ignored_group_attributes: Vec, #[builder(default = "false")] pub verbose: bool, #[builder(default = r#"String::from("server_key")"#)] diff --git a/server/src/infra/ldap_handler.rs b/server/src/infra/ldap_handler.rs index 049f44f..67766e8 100644 --- a/server/src/infra/ldap_handler.rs +++ b/server/src/infra/ldap_handler.rs @@ -102,8 +102,14 @@ fn get_user_id_from_distinguished_name( } } -fn get_user_attribute(user: &User, attribute: &str, dn: &str) -> Result>> { - Ok(Some(match attribute.to_ascii_lowercase().as_str() { +fn get_user_attribute( + user: &User, + attribute: &str, + dn: &str, + ignored_user_attributes: &[String], +) -> Result>> { + let attribute = attribute.to_ascii_lowercase(); + Ok(Some(match attribute.as_str() { "objectclass" => vec![ "inetOrgPerson".to_string(), "posixAccount".to_string(), @@ -118,7 +124,7 @@ fn get_user_attribute(user: &User, attribute: &str, dn: &str) -> Result vec![user.display_name.clone()], "createtimestamp" | "modifytimestamp" => vec![user.creation_date.to_rfc3339()], "1.1" => return Ok(None), - // We ignore the operational attribute wildcard + // We ignore the operational attribute wildcard. "+" => return Ok(None), "*" => { bail!( @@ -127,7 +133,13 @@ fn get_user_attribute(user: &User, attribute: &str, dn: &str) -> Result { - warn!("Ignoring unrecognized group attribute: {}", attribute); + if !ignored_user_attributes.contains(&attribute) { + warn!( + r#"Ignoring unrecognized group attribute: {}\n\ + To disable this warning, add it to "ignored_user_attributes" in the config."#, + attribute + ); + } return Ok(None); } })) @@ -164,6 +176,7 @@ fn make_ldap_search_user_result_entry( user: User, base_dn_str: &str, attributes: &[String], + ignored_user_attributes: &[String], ) -> Result { let dn = format!("uid={},ou=people,{}", user.user_id.as_str(), base_dn_str); @@ -173,7 +186,7 @@ fn make_ldap_search_user_result_entry( attributes: expanded_attributes .iter() .filter_map(|a| { - let values = match get_user_attribute(&user, a, &dn) { + let values = match get_user_attribute(&user, a, &dn, ignored_user_attributes) { Err(e) => return Some(Err(e)), Ok(v) => v, }?; @@ -191,8 +204,10 @@ fn get_group_attribute( base_dn_str: &str, attribute: &str, user_filter: &Option<&UserId>, + ignored_group_attributes: &[String], ) -> Result>> { - Ok(Some(match attribute.to_ascii_lowercase().as_str() { + let attribute = attribute.to_ascii_lowercase(); + Ok(Some(match attribute.as_str() { "objectclass" => vec!["groupOfUniqueNames".to_string()], "dn" | "distinguishedname" => vec![format!( "cn={},ou=groups,{}", @@ -215,7 +230,13 @@ fn get_group_attribute( ) } _ => { - warn!("Ignoring unrecognized group attribute: {}", attribute); + if !ignored_group_attributes.contains(&attribute) { + warn!( + r#"Ignoring unrecognized group attribute: {}\n\ + To disable this warning, add it to "ignored_group_attributes" in the config."#, + attribute + ); + } return Ok(None); } })) @@ -229,6 +250,7 @@ fn make_ldap_search_group_result_entry( base_dn_str: &str, attributes: &[String], user_filter: &Option<&UserId>, + ignored_group_attributes: &[String], ) -> Result { let expanded_attributes = expand_attribute_wildcards(attributes, ALL_GROUP_ATTRIBUTE_KEYS); @@ -237,7 +259,13 @@ fn make_ldap_search_group_result_entry( attributes: expanded_attributes .iter() .filter_map(|a| { - let values = match get_group_attribute(&group, base_dn_str, a, user_filter) { + let values = match get_group_attribute( + &group, + base_dn_str, + a, + user_filter, + ignored_group_attributes, + ) { Err(e) => return Some(Err(e)), Ok(v) => v, }?; @@ -361,10 +389,17 @@ pub struct LdapHandler { backend_handler: Backend, pub base_dn: Vec<(String, String)>, base_dn_str: String, + ignored_user_attributes: Vec, + ignored_group_attributes: Vec, } impl LdapHandler { - pub fn new(backend_handler: Backend, mut ldap_base_dn: String) -> Self { + pub fn new( + backend_handler: Backend, + mut ldap_base_dn: String, + ignored_user_attributes: Vec, + ignored_group_attributes: Vec, + ) -> Self { ldap_base_dn.make_ascii_lowercase(); Self { user_info: None, @@ -376,6 +411,8 @@ impl LdapHandler LdapHandler>>() .unwrap_or_else(|e| { @@ -650,6 +694,7 @@ impl LdapHandler LdapHandler LdapHandler { - warn!(r#"Ignoring unknown user attribute "{}" in filter"#, field); + if !self.ignored_user_attributes.contains(field) { + warn!( + r#"Ignoring unknown user attribute "{}" in filter.\n\ + To disable this warning, add it to "ignored_user_attributes" in the config"#, + field + ); + } Ok(UserRequestFilter::Not(Box::new(UserRequestFilter::And( vec![], )))) @@ -921,7 +975,8 @@ mod tests { set.insert(GroupIdAndName(GroupId(42), "lldap_admin".to_string())); Ok(set) }); - let mut ldap_handler = LdapHandler::new(mock, "dc=Example,dc=com".to_string()); + let mut ldap_handler = + LdapHandler::new(mock, "dc=Example,dc=com".to_string(), vec![], vec![]); let request = LdapBindRequest { dn: "uid=test,ou=people,dc=example,dc=coM".to_string(), cred: LdapBindCred::Simple("pass".to_string()), @@ -946,7 +1001,8 @@ mod tests { mock.expect_get_user_groups() .with(eq(UserId::new("bob"))) .return_once(|_| Ok(HashSet::new())); - let mut ldap_handler = LdapHandler::new(mock, "dc=eXample,dc=com".to_string()); + let mut ldap_handler = + LdapHandler::new(mock, "dc=eXample,dc=com".to_string(), vec![], vec![]); let request = LdapOp::BindRequest(LdapBindRequest { dn: "uid=bob,ou=people,dc=example,dc=com".to_string(), @@ -983,7 +1039,8 @@ mod tests { set.insert(GroupIdAndName(GroupId(42), "lldap_admin".to_string())); Ok(set) }); - let mut ldap_handler = LdapHandler::new(mock, "dc=example,dc=com".to_string()); + let mut ldap_handler = + LdapHandler::new(mock, "dc=example,dc=com".to_string(), vec![], vec![]); let request = LdapBindRequest { dn: "uid=test,ou=people,dc=example,dc=com".to_string(), @@ -1020,7 +1077,8 @@ mod tests { mock.expect_get_user_groups() .with(eq(UserId::new("test"))) .return_once(|_| Ok(HashSet::new())); - let mut ldap_handler = LdapHandler::new(mock, "dc=example,dc=com".to_string()); + let mut ldap_handler = + LdapHandler::new(mock, "dc=example,dc=com".to_string(), vec![], vec![]); let request = LdapBindRequest { dn: "uid=test,ou=people,dc=example,dc=com".to_string(), @@ -1048,7 +1106,8 @@ mod tests { #[tokio::test] async fn test_bind_invalid_dn() { let mock = MockTestBackendHandler::new(); - let mut ldap_handler = LdapHandler::new(mock, "dc=example,dc=com".to_string()); + let mut ldap_handler = + LdapHandler::new(mock, "dc=example,dc=com".to_string(), vec![], vec![]); let request = LdapBindRequest { dn: "cn=bob,dc=example,dc=com".to_string(), @@ -1348,9 +1407,7 @@ mod tests { GroupRequestFilter::Not(Box::new(GroupRequestFilter::Not(Box::new( GroupRequestFilter::And(vec![]), )))), - GroupRequestFilter::Not(Box::new( - GroupRequestFilter::And(vec![]), - )), + GroupRequestFilter::Not(Box::new(GroupRequestFilter::And(vec![]))), ])))) .times(1) .return_once(|_| { diff --git a/server/src/infra/ldap_server.rs b/server/src/infra/ldap_server.rs index 49cf842..38e7fcb 100644 --- a/server/src/infra/ldap_server.rs +++ b/server/src/infra/ldap_server.rs @@ -70,6 +70,8 @@ async fn handle_ldap_stream( stream: Stream, backend_handler: Backend, ldap_base_dn: String, + ignored_user_attributes: Vec, + ignored_group_attributes: Vec, ) -> Result where Backend: BackendHandler + LoginHandler + OpaqueHandler + 'static, @@ -81,7 +83,12 @@ where let mut requests = FramedRead::new(r, LdapCodec); let mut resp = FramedWrite::new(w, LdapCodec); - let mut session = LdapHandler::new(backend_handler, ldap_base_dn); + let mut session = LdapHandler::new( + backend_handler, + ldap_base_dn, + ignored_user_attributes, + ignored_group_attributes, + ); while let Some(msg) = requests.next().await { if !handle_incoming_message(msg, &mut resp, &mut session) @@ -110,7 +117,12 @@ pub fn build_ldap_server( where Backend: BackendHandler + LoginHandler + OpaqueHandler + 'static, { - let context = (backend_handler, config.ldap_base_dn.clone()); + let context = ( + backend_handler, + config.ldap_base_dn.clone(), + config.ignored_user_attributes.clone(), + config.ignored_group_attributes.clone(), + ); let context_for_tls = context.clone(); @@ -119,8 +131,15 @@ where fn_service(move |stream: TcpStream| { let context = context.clone(); async move { - let (handler, base_dn) = context; - handle_ldap_stream(stream, handler, base_dn).await + let (handler, base_dn, ignored_user_attributes, ignored_group_attributes) = context; + handle_ldap_stream( + stream, + handler, + base_dn, + ignored_user_attributes, + ignored_group_attributes, + ) + .await } }) .map_err(|err: anyhow::Error| error!("[LDAP] Service Error: {:#}", err)) @@ -139,9 +158,19 @@ where fn_service(move |stream: TcpStream| { let tls_context = tls_context.clone(); async move { - let ((handler, base_dn), tls_acceptor) = tls_context; + let ( + (handler, base_dn, ignored_user_attributes, ignored_group_attributes), + tls_acceptor, + ) = tls_context; let tls_stream = tls_acceptor.accept(stream).await?; - handle_ldap_stream(tls_stream, handler, base_dn).await + handle_ldap_stream( + tls_stream, + handler, + base_dn, + ignored_user_attributes, + ignored_group_attributes, + ) + .await } }) .map_err(|err: anyhow::Error| error!("[LDAPS] Service Error: {:#}", err))