diff --git a/Cargo.lock b/Cargo.lock index 02e5b0f..6d7684c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2867,7 +2867,6 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "reqwest 0.11.27", - "reqwest 0.12.28", "rustls 0.23.35", "rustls-pemfile 2.2.0", "rustls-pki-types", diff --git a/crates/ldap/src/core/group.rs b/crates/ldap/src/core/group.rs index b11a283..2c54a8c 100644 --- a/crates/ldap/src/core/group.rs +++ b/crates/ldap/src/core/group.rs @@ -173,22 +173,31 @@ fn get_group_attribute_equality_filter( typ: AttributeType, is_list: bool, value: &str, -) -> GroupRequestFilter { +) -> LdapResult { + if is_list { + return Err(LdapError { + code: LdapResultCode::UnwillingToPerform, + message: format!( + "Equality filter on list attribute \"{}\" is not supported", + field + ), + }); + } let value_lc = value.to_ascii_lowercase(); - let serialized_value = deserialize_attribute_value(&[value.to_owned()], typ, is_list); - let serialized_value_lc = deserialize_attribute_value(&[value_lc.to_owned()], typ, is_list); + let serialized_value = deserialize_attribute_value(&[value.to_owned()], typ, false); + let serialized_value_lc = deserialize_attribute_value(&[value_lc.to_owned()], typ, false); match (serialized_value, serialized_value_lc) { - (Ok(v), Ok(v_lc)) => GroupRequestFilter::Or(vec![ + (Ok(v), Ok(v_lc)) => Ok(GroupRequestFilter::Or(vec![ GroupRequestFilter::AttributeEquality(field.clone(), v), GroupRequestFilter::AttributeEquality(field.clone(), v_lc), - ]), + ])), (Ok(_), Err(e)) => { warn!("Invalid value for attribute {} (lowercased): {}", field, e); - GroupRequestFilter::False + Ok(GroupRequestFilter::False) } (Err(e), _) => { warn!("Invalid value for attribute {}: {}", field, e); - GroupRequestFilter::False + Ok(GroupRequestFilter::False) } } } @@ -259,9 +268,9 @@ fn convert_group_filter( } Ok(GroupRequestFilter::False) } - GroupFieldType::Attribute(field, typ, is_list) => Ok( - get_group_attribute_equality_filter(&field, typ, is_list, value), - ), + GroupFieldType::Attribute(field, typ, is_list) => { + get_group_attribute_equality_filter(&field, typ, is_list, value) + } GroupFieldType::CreationDate => Err(LdapError { code: LdapResultCode::UnwillingToPerform, message: "Creation date filter for groups not supported".to_owned(), @@ -671,6 +680,83 @@ mod tests { ); } + #[tokio::test] + async fn test_equality_filter_on_list_group_attribute_returns_error() { + use lldap_domain::schema::{AttributeList, AttributeSchema, Schema}; + let mut mock = MockTestBackendHandler::new(); + mock.expect_get_schema().returning(|| { + Ok(Schema { + user_attributes: AttributeList { + attributes: Vec::new(), + }, + group_attributes: AttributeList { + attributes: vec![AttributeSchema { + name: "tags".into(), + attribute_type: AttributeType::String, + is_list: true, + is_visible: true, + is_editable: true, + is_hardcoded: false, + is_readonly: false, + }], + }, + extra_user_object_classes: Vec::new(), + extra_group_object_classes: Vec::new(), + }) + }); + let ldap_handler = setup_bound_admin_handler(mock).await; + let request = make_group_search_request( + LdapFilter::Equality("tags".to_string(), "foo".to_string()), + vec!["dn"], + ); + assert_eq!( + ldap_handler.do_search_or_dse(&request).await, + Err(LdapError { + code: LdapResultCode::UnwillingToPerform, + message: r#"Equality filter on list attribute "tags" is not supported"# + .to_string(), + }) + ); + } + + #[tokio::test] + async fn test_equality_filter_on_non_list_group_attribute() { + use lldap_domain::schema::{AttributeList, AttributeSchema, Schema}; + let mut mock = MockTestBackendHandler::new(); + mock.expect_get_schema().returning(|| { + Ok(Schema { + user_attributes: AttributeList { + attributes: Vec::new(), + }, + group_attributes: AttributeList { + attributes: vec![AttributeSchema { + name: "club_name".into(), + attribute_type: AttributeType::String, + is_list: false, + is_visible: true, + is_editable: true, + is_hardcoded: false, + is_readonly: false, + }], + }, + extra_user_object_classes: Vec::new(), + extra_group_object_classes: Vec::new(), + }) + }); + mock.expect_list_groups() + .times(1) + .return_once(|_| Ok(vec![])); + let ldap_handler = setup_bound_admin_handler(mock).await; + let request = make_group_search_request( + LdapFilter::Equality("club_name".to_string(), "myclub".to_string()), + vec!["dn"], + ); + assert_eq!( + ldap_handler.do_search_or_dse(&request).await, + Ok(vec![make_search_success()]) + ); + } + #[tokio::test] async fn test_search_group_as_scope() { let mut mock = MockTestBackendHandler::new(); @@ -690,3 +776,4 @@ mod tests { ); } } + diff --git a/crates/ldap/src/core/user.rs b/crates/ldap/src/core/user.rs index 839efa1..e73a7d6 100644 --- a/crates/ldap/src/core/user.rs +++ b/crates/ldap/src/core/user.rs @@ -182,22 +182,31 @@ fn get_user_attribute_equality_filter( typ: AttributeType, is_list: bool, value: &str, -) -> UserRequestFilter { +) -> LdapResult { + if is_list { + return Err(LdapError { + code: LdapResultCode::UnwillingToPerform, + message: format!( + "Equality filter on list attribute \"{}\" is not supported", + field + ), + }); + } let value_lc = value.to_ascii_lowercase(); - let serialized_value = deserialize_attribute_value(&[value.to_owned()], typ, is_list); - let serialized_value_lc = deserialize_attribute_value(&[value_lc.to_owned()], typ, is_list); + let serialized_value = deserialize_attribute_value(&[value.to_owned()], typ, false); + let serialized_value_lc = deserialize_attribute_value(&[value_lc.to_owned()], typ, false); match (serialized_value, serialized_value_lc) { - (Ok(v), Ok(v_lc)) => UserRequestFilter::Or(vec![ + (Ok(v), Ok(v_lc)) => Ok(UserRequestFilter::Or(vec![ UserRequestFilter::AttributeEquality(field.clone(), v), UserRequestFilter::AttributeEquality(field.clone(), v_lc), - ]), + ])), (Ok(_), Err(e)) => { warn!("Invalid value for attribute {} (lowercased): {}", field, e); - UserRequestFilter::False + Ok(UserRequestFilter::False) } (Err(e), _) => { warn!("Invalid value for attribute {}: {}", field, e); - UserRequestFilter::False + Ok(UserRequestFilter::False) } } } @@ -279,9 +288,9 @@ fn convert_user_filter( UserFieldType::PrimaryField(field) => { Ok(UserRequestFilter::Equality(field, value_lc)) } - UserFieldType::Attribute(field, typ, is_list) => Ok( - get_user_attribute_equality_filter(&field, typ, is_list, value), - ), + UserFieldType::Attribute(field, typ, is_list) => { + get_user_attribute_equality_filter(&field, typ, is_list, value) + } UserFieldType::NoMatch => { if !ldap_info.ignored_user_attributes.contains(&field) { warn!( @@ -786,6 +795,83 @@ mod tests { } } + #[tokio::test] + async fn test_equality_filter_on_list_user_attribute_returns_error() { + use lldap_domain::schema::{AttributeList, AttributeSchema, Schema}; + let mut mock = MockTestBackendHandler::new(); + mock.expect_get_schema().returning(|| { + Ok(Schema { + user_attributes: AttributeList { + attributes: vec![AttributeSchema { + name: "mailalias".into(), + attribute_type: AttributeType::String, + is_list: true, + is_visible: true, + is_editable: true, + is_hardcoded: false, + is_readonly: false, + }], + }, + group_attributes: AttributeList { + attributes: Vec::new(), + }, + extra_user_object_classes: Vec::new(), + extra_group_object_classes: Vec::new(), + }) + }); + let ldap_handler = setup_bound_admin_handler(mock).await; + let request = make_user_search_request( + LdapFilter::Equality("mailalias".to_string(), "alias@example.com".to_string()), + vec!["dn"], + ); + assert_eq!( + ldap_handler.do_search_or_dse(&request).await, + Err(LdapError { + code: LdapResultCode::UnwillingToPerform, + message: r#"Equality filter on list attribute "mailalias" is not supported"# + .to_string(), + }) + ); + } + + #[tokio::test] + async fn test_equality_filter_on_non_list_user_attribute() { + use lldap_domain::schema::{AttributeList, AttributeSchema, Schema}; + let mut mock = MockTestBackendHandler::new(); + mock.expect_get_schema().returning(|| { + Ok(Schema { + user_attributes: AttributeList { + attributes: vec![AttributeSchema { + name: "nickname".into(), + attribute_type: AttributeType::String, + is_list: false, + is_visible: true, + is_editable: true, + is_hardcoded: false, + is_readonly: false, + }], + }, + group_attributes: AttributeList { + attributes: Vec::new(), + }, + extra_user_object_classes: Vec::new(), + extra_group_object_classes: Vec::new(), + }) + }); + mock.expect_list_users() + .times(1) + .return_once(|_, _| Ok(vec![])); + let ldap_handler = setup_bound_admin_handler(mock).await; + let request = make_user_search_request( + LdapFilter::Equality("nickname".to_string(), "alice".to_string()), + vec!["dn"], + ); + assert_eq!( + ldap_handler.do_search_or_dse(&request).await, + Ok(vec![make_search_success()]) + ); + } + #[tokio::test] async fn test_search_cn_case_insensitive() { use lldap_domain::uuid;