mirror of
				https://github.com/meilisearch/meilisearch.git
				synced 2025-10-26 05:26:27 +00:00 
			
		
		
		
	Merge #3945
3945: Do not leak field information on error r=Kerollmops a=vivek-26 # Pull Request ## Related issue Fixes #3865 ## What does this PR do? This PR ensures that `InvalidSortableAttribute`and `InvalidFacetSearchFacetName` errors do not leak field information i.e. fields which are not part of `displayedAttributes` in the settings are hidden from the error message. ## PR checklist Please check if your PR fulfills the following requirements: - [x] Does this PR fix an existing issue, or have you listed the changes applied in the PR description (and why they are needed)? - [x] Have you read the contributing guidelines? - [x] Have you made sure that the title is accurate and descriptive of the changes? Thank you so much for contributing to Meilisearch! Co-authored-by: Vivek Kumar <vivek.26@outlook.com>
This commit is contained in:
		| @@ -122,22 +122,28 @@ only composed of alphanumeric characters (a-z A-Z 0-9), hyphens (-) and undersco | ||||
|         .field, | ||||
|         match .valid_fields.is_empty() { | ||||
|             true => "This index does not have configured sortable attributes.".to_string(), | ||||
|             false => format!("Available sortable attributes are: `{}`.", | ||||
|                     valid_fields.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", ") | ||||
|             false => format!("Available sortable attributes are: `{}{}`.", | ||||
|                     valid_fields.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", "), | ||||
|                     .hidden_fields.then_some(", <..hidden-attributes>").unwrap_or(""), | ||||
|                 ), | ||||
|         } | ||||
|     )] | ||||
|     InvalidSortableAttribute { field: String, valid_fields: BTreeSet<String> }, | ||||
|     InvalidSortableAttribute { field: String, valid_fields: BTreeSet<String>, hidden_fields: bool }, | ||||
|     #[error("Attribute `{}` is not facet-searchable. {}", | ||||
|         .field, | ||||
|         match .valid_fields.is_empty() { | ||||
|             true => "This index does not have configured facet-searchable attributes. To make it facet-searchable add it to the `filterableAttributes` index settings.".to_string(), | ||||
|             false => format!("Available facet-searchable attributes are: `{}`. To make it facet-searchable add it to the `filterableAttributes` index settings.", | ||||
|                     valid_fields.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", ") | ||||
|             false => format!("Available facet-searchable attributes are: `{}{}`. To make it facet-searchable add it to the `filterableAttributes` index settings.", | ||||
|                     valid_fields.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", "), | ||||
|                     .hidden_fields.then_some(", <..hidden-attributes>").unwrap_or(""), | ||||
|                 ), | ||||
|         } | ||||
|     )] | ||||
|     InvalidFacetSearchFacetName { field: String, valid_fields: BTreeSet<String> }, | ||||
|     InvalidFacetSearchFacetName { | ||||
|         field: String, | ||||
|         valid_fields: BTreeSet<String>, | ||||
|         hidden_fields: bool, | ||||
|     }, | ||||
|     #[error("Attribute `{}` is not searchable. Available searchable attributes are: `{}{}`.", | ||||
|         .field, | ||||
|         .valid_fields.iter().map(AsRef::as_ref).collect::<Vec<&str>>().join(", "), | ||||
| @@ -340,8 +346,11 @@ fn conditionally_lookup_for_error_message() { | ||||
|     ]; | ||||
|  | ||||
|     for (list, suffix) in messages { | ||||
|         let err = | ||||
|             UserError::InvalidSortableAttribute { field: "name".to_string(), valid_fields: list }; | ||||
|         let err = UserError::InvalidSortableAttribute { | ||||
|             field: "name".to_string(), | ||||
|             valid_fields: list, | ||||
|             hidden_fields: false, | ||||
|         }; | ||||
|  | ||||
|         assert_eq!(err.to_string(), format!("{} {}", prefix, suffix)); | ||||
|     } | ||||
|   | ||||
| @@ -655,6 +655,26 @@ impl Index { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* remove hidden fields */ | ||||
|     pub fn remove_hidden_fields( | ||||
|         &self, | ||||
|         rtxn: &RoTxn, | ||||
|         fields: impl IntoIterator<Item = impl AsRef<str>>, | ||||
|     ) -> Result<(BTreeSet<String>, bool)> { | ||||
|         let mut valid_fields = | ||||
|             fields.into_iter().map(|f| f.as_ref().to_string()).collect::<BTreeSet<String>>(); | ||||
|  | ||||
|         let fields_len = valid_fields.len(); | ||||
|  | ||||
|         if let Some(dn) = self.displayed_fields(rtxn)? { | ||||
|             let displayable_names = dn.iter().map(|s| s.to_string()).collect(); | ||||
|             valid_fields = &valid_fields & &displayable_names; | ||||
|         } | ||||
|  | ||||
|         let hidden_fields = fields_len > valid_fields.len(); | ||||
|         Ok((valid_fields, hidden_fields)) | ||||
|     } | ||||
|  | ||||
|     /* searchable fields */ | ||||
|  | ||||
|     /// Write the user defined searchable fields and generate the real searchable fields from the specified fields ids map. | ||||
|   | ||||
| @@ -280,9 +280,13 @@ impl<'a> SearchForFacetValues<'a> { | ||||
|  | ||||
|         let filterable_fields = index.filterable_fields(rtxn)?; | ||||
|         if !filterable_fields.contains(&self.facet) { | ||||
|             let (valid_fields, hidden_fields) = | ||||
|                 index.remove_hidden_fields(rtxn, filterable_fields)?; | ||||
|  | ||||
|             return Err(UserError::InvalidFacetSearchFacetName { | ||||
|                 field: self.facet.clone(), | ||||
|                 valid_fields: filterable_fields.into_iter().collect(), | ||||
|                 valid_fields, | ||||
|                 hidden_fields, | ||||
|             } | ||||
|             .into()); | ||||
|         } | ||||
|   | ||||
| @@ -20,7 +20,7 @@ mod sort; | ||||
| #[cfg(test)] | ||||
| mod tests; | ||||
|  | ||||
| use std::collections::{BTreeSet, HashSet}; | ||||
| use std::collections::HashSet; | ||||
|  | ||||
| use bucket_sort::{bucket_sort, BucketSortOutput}; | ||||
| use charabia::TokenizerBuilder; | ||||
| @@ -108,24 +108,11 @@ impl<'ctx> SearchContext<'ctx> { | ||||
|                 (None, None) => continue, | ||||
|                 // The field is not searchable => User error | ||||
|                 (_fid, Some(false)) => { | ||||
|                     let mut valid_fields: BTreeSet<_> = | ||||
|                         fids_map.names().map(String::from).collect(); | ||||
|                     let (valid_fields, hidden_fields) = match searchable_names { | ||||
|                         Some(sn) => self.index.remove_hidden_fields(self.txn, sn)?, | ||||
|                         None => self.index.remove_hidden_fields(self.txn, fids_map.names())?, | ||||
|                     }; | ||||
|  | ||||
|                     // Filter by the searchable names | ||||
|                     if let Some(sn) = searchable_names { | ||||
|                         let searchable_names = sn.iter().map(|s| s.to_string()).collect(); | ||||
|                         valid_fields = &valid_fields & &searchable_names; | ||||
|                     } | ||||
|  | ||||
|                     let searchable_count = valid_fields.len(); | ||||
|  | ||||
|                     // Remove hidden fields | ||||
|                     if let Some(dn) = self.index.displayed_fields(self.txn)? { | ||||
|                         let displayable_names = dn.iter().map(|s| s.to_string()).collect(); | ||||
|                         valid_fields = &valid_fields & &displayable_names; | ||||
|                     } | ||||
|  | ||||
|                     let hidden_fields = searchable_count > valid_fields.len(); | ||||
|                     let field = field_name.to_string(); | ||||
|                     return Err(UserError::InvalidSearchableAttribute { | ||||
|                         field, | ||||
| @@ -604,16 +591,24 @@ fn check_sort_criteria(ctx: &SearchContext, sort_criteria: Option<&Vec<AscDesc>> | ||||
|     for asc_desc in sort_criteria { | ||||
|         match asc_desc.member() { | ||||
|             Member::Field(ref field) if !crate::is_faceted(field, &sortable_fields) => { | ||||
|                 let (valid_fields, hidden_fields) = | ||||
|                     ctx.index.remove_hidden_fields(ctx.txn, sortable_fields)?; | ||||
|  | ||||
|                 return Err(UserError::InvalidSortableAttribute { | ||||
|                     field: field.to_string(), | ||||
|                     valid_fields: sortable_fields.into_iter().collect(), | ||||
|                 })? | ||||
|                     valid_fields, | ||||
|                     hidden_fields, | ||||
|                 })?; | ||||
|             } | ||||
|             Member::Geo(_) if !sortable_fields.contains("_geo") => { | ||||
|                 let (valid_fields, hidden_fields) = | ||||
|                     ctx.index.remove_hidden_fields(ctx.txn, sortable_fields)?; | ||||
|  | ||||
|                 return Err(UserError::InvalidSortableAttribute { | ||||
|                     field: "_geo".to_string(), | ||||
|                     valid_fields: sortable_fields.into_iter().collect(), | ||||
|                 })? | ||||
|                     valid_fields, | ||||
|                     hidden_fields, | ||||
|                 })?; | ||||
|             } | ||||
|             _ => (), | ||||
|         } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user